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({super.key, required super.child, this.onNotification});
103
104 /// Called when a notification of the appropriate type arrives at this
105 /// location in the tree.
106 ///
107 /// Return true to cancel the notification bubbling. Return false to
108 /// allow the notification to continue to be dispatched to further ancestors.
109 ///
110 /// Notifications vary in terms of when they are dispatched. There are two
111 /// main possibilities: dispatch between frames, and dispatch during layout.
112 ///
113 /// For notifications that dispatch during layout, such as those that inherit
114 /// from [LayoutChangedNotification], it is too late to call [State.setState]
115 /// in response to the notification (as layout is currently happening in a
116 /// descendant, by definition, since notifications bubble up the tree). For
117 /// widgets that depend on layout, consider a [LayoutBuilder] instead.
118 final NotificationListenerCallback<T>? onNotification;
119
120 @override
121 Element createElement() {
122 return _NotificationElement<T>(this);
123 }
124}
125
126/// An element used to host [NotificationListener] elements.
127class _NotificationElement<T extends Notification> extends ProxyElement
128 with NotifiableElementMixin {
129 _NotificationElement(NotificationListener<T> super.widget);
130
131 @override
132 bool onNotification(Notification notification) {
133 final NotificationListener<T> listener = widget as NotificationListener<T>;
134 if (listener.onNotification != null && notification is T) {
135 return listener.onNotification!(notification);
136 }
137 return false;
138 }
139
140 @override
141 void notifyClients(covariant ProxyWidget oldWidget) {
142 // Notification tree does not need to notify clients.
143 }
144}
145
146/// Indicates that the layout of one of the descendants of the object receiving
147/// this notification has changed in some way, and that therefore any
148/// assumptions about that layout are no longer valid.
149///
150/// Useful if, for instance, you're trying to align multiple descendants.
151///
152/// To listen for notifications in a subtree, use a
153/// [NotificationListener<LayoutChangedNotification>].
154///
155/// To send a notification, call [dispatch] on the notification you wish to
156/// send. The notification will be delivered to any [NotificationListener]
157/// widgets with the appropriate type parameters that are ancestors of the given
158/// [BuildContext].
159///
160/// In the widgets library, only the [SizeChangedLayoutNotifier] class and
161/// [Scrollable] classes dispatch this notification (specifically, they dispatch
162/// [SizeChangedLayoutNotification]s and [ScrollNotification]s respectively).
163/// Transitions, in particular, do not. Changing one's layout in one's build
164/// function does not cause this notification to be dispatched automatically. If
165/// an ancestor expects to be notified for any layout change, make sure you
166/// either only use widgets that never change layout, or that notify their
167/// ancestors when appropriate, or alternatively, dispatch the notifications
168/// yourself when appropriate.
169///
170/// Also, since this notification is sent when the layout is changed, it is only
171/// useful for paint effects that depend on the layout. If you were to use this
172/// notification to change the build, for instance, you would always be one
173/// frame behind, which would look really ugly and laggy.
174class LayoutChangedNotification extends Notification {
175 /// Create a new [LayoutChangedNotification].
176 const LayoutChangedNotification();
177}
178