1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'layout_builder.dart';
6/// @docImport 'nested_scroll_view.dart';
7/// @docImport 'scroll_notification.dart';
8/// @docImport 'scroll_view.dart';
9/// @docImport 'scrollable.dart';
10/// @docImport 'size_changed_layout_notifier.dart';
11library;
12
13import 'package:flutter/foundation.dart';
14
15import 'framework.dart';
16
17/// Signature for [Notification] listeners.
18///
19/// Return true to cancel the notification bubbling. Return false to allow the
20/// notification to continue to be dispatched to further ancestors.
21///
22/// [NotificationListener] is useful when listening scroll events
23/// in [ListView],[NestedScrollView],[GridView] or any Scrolling widgets.
24/// Used by [NotificationListener.onNotification].
25typedef NotificationListenerCallback<T extends Notification> = bool Function(T notification);
26
27/// A notification that can bubble up the widget tree.
28///
29/// You can determine the type of a notification using the `is` operator to
30/// check the [runtimeType] of the notification.
31///
32/// To listen for notifications in a subtree, use a [NotificationListener].
33///
34/// To send a notification, call [dispatch] on the notification you wish to
35/// send. The notification will be delivered to any [NotificationListener]
36/// widgets with the appropriate type parameters that are ancestors of the given
37/// [BuildContext].
38///
39/// {@tool dartpad}
40/// This example shows a [NotificationListener] widget
41/// that listens for [ScrollNotification] notifications. When a scroll
42/// event occurs in the [NestedScrollView],
43/// this widget is notified. The events could be either a
44/// [ScrollStartNotification]or[ScrollEndNotification].
45///
46/// ** See code in examples/api/lib/widgets/notification_listener/notification.0.dart **
47/// {@end-tool}
48///
49/// See also:
50///
51/// * [ScrollNotification] which describes the notification lifecycle.
52/// * [ScrollStartNotification] which returns the start position of scrolling.
53/// * [ScrollEndNotification] which returns the end position of scrolling.
54/// * [NestedScrollView] which creates a nested scroll view.
55///
56abstract class Notification {
57 /// Abstract const constructor. This constructor enables subclasses to provide
58 /// const constructors so that they can be used in const expressions.
59 const Notification();
60
61 /// Start bubbling this notification at the given build context.
62 ///
63 /// The notification will be delivered to any [NotificationListener] widgets
64 /// with the appropriate type parameters that are ancestors of the given
65 /// [BuildContext]. If the [BuildContext] is null, the notification is not
66 /// dispatched.
67 void dispatch(BuildContext? target) {
68 target?.dispatchNotification(this);
69 }
70
71 @override
72 String toString() {
73 final List<String> description = <String>[];
74 debugFillDescription(description);
75 return '${objectRuntimeType(this, 'Notification')}(${description.join(", ")})';
76 }
77
78 /// Add additional information to the given description for use by [toString].
79 ///
80 /// This method makes it easier for subclasses to coordinate to provide a
81 /// high-quality [toString] implementation. The [toString] implementation on
82 /// the [Notification] base class calls [debugFillDescription] to collect
83 /// useful information from subclasses to incorporate into its return value.
84 ///
85 /// Implementations of this method should start with a call to the inherited
86 /// method, as in `super.debugFillDescription(description)`.
87 @protected
88 @mustCallSuper
89 void debugFillDescription(List<String> description) { }
90}
91
92/// A widget that listens for [Notification]s bubbling up the tree.
93///
94/// {@youtube 560 315 https://www.youtube.com/watch?v=cAnFbFoGM50}
95///
96/// Notifications will trigger the [onNotification] callback only if their
97/// [runtimeType] is a subtype of `T`.
98///
99/// To dispatch notifications, use the [Notification.dispatch] method.
100class NotificationListener<T extends Notification> extends ProxyWidget {
101 /// Creates a widget that listens for notifications.
102 const NotificationListener({
103 super.key,
104 required super.child,
105 this.onNotification,
106 });
107
108 /// Called when a notification of the appropriate type arrives at this
109 /// location in the tree.
110 ///
111 /// Return true to cancel the notification bubbling. Return false to
112 /// allow the notification to continue to be dispatched to further ancestors.
113 ///
114 /// Notifications vary in terms of when they are dispatched. There are two
115 /// main possibilities: dispatch between frames, and dispatch during layout.
116 ///
117 /// For notifications that dispatch during layout, such as those that inherit
118 /// from [LayoutChangedNotification], it is too late to call [State.setState]
119 /// in response to the notification (as layout is currently happening in a
120 /// descendant, by definition, since notifications bubble up the tree). For
121 /// widgets that depend on layout, consider a [LayoutBuilder] instead.
122 final NotificationListenerCallback<T>? onNotification;
123
124 @override
125 Element createElement() {
126 return _NotificationElement<T>(this);
127 }
128}
129
130/// An element used to host [NotificationListener] elements.
131class _NotificationElement<T extends Notification> extends ProxyElement with NotifiableElementMixin {
132 _NotificationElement(NotificationListener<T> super.widget);
133
134 @override
135 bool onNotification(Notification notification) {
136 final NotificationListener<T> listener = widget as NotificationListener<T>;
137 if (listener.onNotification != null && notification is T) {
138 return listener.onNotification!(notification);
139 }
140 return false;
141 }
142
143 @override
144 void notifyClients(covariant ProxyWidget oldWidget) {
145 // Notification tree does not need to notify clients.
146 }
147}
148
149/// Indicates that the layout of one of the descendants of the object receiving
150/// this notification has changed in some way, and that therefore any
151/// assumptions about that layout are no longer valid.
152///
153/// Useful if, for instance, you're trying to align multiple descendants.
154///
155/// To listen for notifications in a subtree, use a
156/// [NotificationListener<LayoutChangedNotification>].
157///
158/// To send a notification, call [dispatch] on the notification you wish to
159/// send. The notification will be delivered to any [NotificationListener]
160/// widgets with the appropriate type parameters that are ancestors of the given
161/// [BuildContext].
162///
163/// In the widgets library, only the [SizeChangedLayoutNotifier] class and
164/// [Scrollable] classes dispatch this notification (specifically, they dispatch
165/// [SizeChangedLayoutNotification]s and [ScrollNotification]s respectively).
166/// Transitions, in particular, do not. Changing one's layout in one's build
167/// function does not cause this notification to be dispatched automatically. If
168/// an ancestor expects to be notified for any layout change, make sure you
169/// either only use widgets that never change layout, or that notify their
170/// ancestors when appropriate, or alternatively, dispatch the notifications
171/// yourself when appropriate.
172///
173/// Also, since this notification is sent when the layout is changed, it is only
174/// useful for paint effects that depend on the layout. If you were to use this
175/// notification to change the build, for instance, you would always be one
176/// frame behind, which would look really ugly and laggy.
177class LayoutChangedNotification extends Notification {
178 /// Create a new [LayoutChangedNotification].
179 const LayoutChangedNotification();
180}
181