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