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 'animation.dart';
8
9export 'dart:ui' show VoidCallback;
10
11export 'animation.dart' show AnimationStatus, AnimationStatusListener;
12
13/// A mixin that helps listen to another object only when this object has registered listeners.
14///
15/// This mixin provides implementations of [didRegisterListener] and [didUnregisterListener],
16/// and therefore can be used in conjunction with mixins that require these methods,
17/// [AnimationLocalListenersMixin] and [AnimationLocalStatusListenersMixin].
18mixin AnimationLazyListenerMixin {
19 int _listenerCounter = 0;
20
21 /// Calls [didStartListening] every time a registration of a listener causes
22 /// an empty list of listeners to become non-empty.
23 ///
24 /// See also:
25 ///
26 /// * [didUnregisterListener], which may cause the listener list to
27 /// become empty again, and in turn cause this method to call
28 /// [didStartListening] again.
29 @protected
30 void didRegisterListener() {
31 assert(_listenerCounter >= 0);
32 if (_listenerCounter == 0) {
33 didStartListening();
34 }
35 _listenerCounter += 1;
36 }
37
38 /// Calls [didStopListening] when an only remaining listener is unregistered,
39 /// thus making the list empty.
40 ///
41 /// See also:
42 ///
43 /// * [didRegisterListener], which causes the listener list to become non-empty.
44 @protected
45 void didUnregisterListener() {
46 assert(_listenerCounter >= 1);
47 _listenerCounter -= 1;
48 if (_listenerCounter == 0) {
49 didStopListening();
50 }
51 }
52
53 /// Called when the number of listeners changes from zero to one.
54 @protected
55 void didStartListening();
56
57 /// Called when the number of listeners changes from one to zero.
58 @protected
59 void didStopListening();
60
61 /// Whether there are any listeners.
62 bool get isListening => _listenerCounter > 0;
63}
64
65/// A mixin that replaces the [didRegisterListener]/[didUnregisterListener] contract
66/// with a dispose contract.
67///
68/// This mixin provides implementations of [didRegisterListener] and [didUnregisterListener],
69/// and therefore can be used in conjunction with mixins that require these methods,
70/// [AnimationLocalListenersMixin] and [AnimationLocalStatusListenersMixin].
71mixin AnimationEagerListenerMixin {
72 /// This implementation ignores listener registrations.
73 @protected
74 void didRegisterListener() {}
75
76 /// This implementation ignores listener registrations.
77 @protected
78 void didUnregisterListener() {}
79
80 /// Release the resources used by this object. The object is no longer usable
81 /// after this method is called.
82 @mustCallSuper
83 void dispose() {}
84}
85
86/// A mixin that implements the [addListener]/[removeListener] protocol and notifies
87/// all the registered listeners when [notifyListeners] is called.
88///
89/// This mixin requires that the mixing class provide methods [didRegisterListener]
90/// and [didUnregisterListener]. Implementations of these methods can be obtained
91/// by mixing in another mixin from this library, such as [AnimationLazyListenerMixin].
92mixin AnimationLocalListenersMixin {
93 final HashedObserverList<VoidCallback> _listeners = HashedObserverList<VoidCallback>();
94
95 /// Called immediately before a listener is added via [addListener].
96 ///
97 /// At the time this method is called the registered listener is not yet
98 /// notified by [notifyListeners].
99 @protected
100 void didRegisterListener();
101
102 /// Called immediately after a listener is removed via [removeListener].
103 ///
104 /// At the time this method is called the removed listener is no longer
105 /// notified by [notifyListeners].
106 @protected
107 void didUnregisterListener();
108
109 /// Calls the listener every time the value of the animation changes.
110 ///
111 /// Listeners can be removed with [removeListener].
112 void addListener(VoidCallback listener) {
113 didRegisterListener();
114 _listeners.add(listener);
115 }
116
117 /// Stop calling the listener every time the value of the animation changes.
118 ///
119 /// Listeners can be added with [addListener].
120 void removeListener(VoidCallback listener) {
121 final bool removed = _listeners.remove(listener);
122 if (removed) {
123 didUnregisterListener();
124 }
125 }
126
127 /// Removes all listeners added with [addListener].
128 ///
129 /// This method is typically called from the `dispose` method of the class
130 /// using this mixin if the class also uses the [AnimationEagerListenerMixin].
131 ///
132 /// Calling this method will not trigger [didUnregisterListener].
133 @protected
134 void clearListeners() {
135 _listeners.clear();
136 }
137
138 /// Calls all the listeners.
139 ///
140 /// If listeners are added or removed during this function, the modifications
141 /// will not change which listeners are called during this iteration.
142 @protected
143 @pragma('vm:notify-debugger-on-exception')
144 void notifyListeners() {
145 final List<VoidCallback> localListeners = _listeners.toList(growable: false);
146 for (final VoidCallback listener in localListeners) {
147 InformationCollector? collector;
148 assert(() {
149 collector = () => <DiagnosticsNode>[
150 DiagnosticsProperty<AnimationLocalListenersMixin>(
151 'The $runtimeType notifying listeners was',
152 this,
153 style: DiagnosticsTreeStyle.errorProperty,
154 ),
155 ];
156 return true;
157 }());
158 try {
159 if (_listeners.contains(listener)) {
160 listener();
161 }
162 } catch (exception, stack) {
163 FlutterError.reportError(
164 FlutterErrorDetails(
165 exception: exception,
166 stack: stack,
167 library: 'animation library',
168 context: ErrorDescription('while notifying listeners for $runtimeType'),
169 informationCollector: collector,
170 ),
171 );
172 }
173 }
174 }
175}
176
177/// A mixin that implements the addStatusListener/removeStatusListener protocol
178/// and notifies all the registered listeners when notifyStatusListeners is
179/// called.
180///
181/// This mixin requires that the mixing class provide methods [didRegisterListener]
182/// and [didUnregisterListener]. Implementations of these methods can be obtained
183/// by mixing in another mixin from this library, such as [AnimationLazyListenerMixin].
184mixin AnimationLocalStatusListenersMixin {
185 final ObserverList<AnimationStatusListener> _statusListeners =
186 ObserverList<AnimationStatusListener>();
187
188 /// Called immediately before a status listener is added via [addStatusListener].
189 ///
190 /// At the time this method is called the registered listener is not yet
191 /// notified by [notifyStatusListeners].
192 @protected
193 void didRegisterListener();
194
195 /// Called immediately after a status listener is removed via [removeStatusListener].
196 ///
197 /// At the time this method is called the removed listener is no longer
198 /// notified by [notifyStatusListeners].
199 @protected
200 void didUnregisterListener();
201
202 /// Calls listener every time the status of the animation changes.
203 ///
204 /// Listeners can be removed with [removeStatusListener].
205 void addStatusListener(AnimationStatusListener listener) {
206 didRegisterListener();
207 _statusListeners.add(listener);
208 }
209
210 /// Stops calling the listener every time the status of the animation changes.
211 ///
212 /// Listeners can be added with [addStatusListener].
213 void removeStatusListener(AnimationStatusListener listener) {
214 final bool removed = _statusListeners.remove(listener);
215 if (removed) {
216 didUnregisterListener();
217 }
218 }
219
220 /// Removes all listeners added with [addStatusListener].
221 ///
222 /// This method is typically called from the `dispose` method of the class
223 /// using this mixin if the class also uses the [AnimationEagerListenerMixin].
224 ///
225 /// Calling this method will not trigger [didUnregisterListener].
226 @protected
227 void clearStatusListeners() {
228 _statusListeners.clear();
229 }
230
231 /// Calls all the status listeners.
232 ///
233 /// If listeners are added or removed during this function, the modifications
234 /// will not change which listeners are called during this iteration.
235 @protected
236 @pragma('vm:notify-debugger-on-exception')
237 void notifyStatusListeners(AnimationStatus status) {
238 final List<AnimationStatusListener> localListeners = _statusListeners.toList(growable: false);
239 for (final AnimationStatusListener listener in localListeners) {
240 try {
241 if (_statusListeners.contains(listener)) {
242 listener(status);
243 }
244 } catch (exception, stack) {
245 InformationCollector? collector;
246 assert(() {
247 collector = () => <DiagnosticsNode>[
248 DiagnosticsProperty<AnimationLocalStatusListenersMixin>(
249 'The $runtimeType notifying status listeners was',
250 this,
251 style: DiagnosticsTreeStyle.errorProperty,
252 ),
253 ];
254 return true;
255 }());
256 FlutterError.reportError(
257 FlutterErrorDetails(
258 exception: exception,
259 stack: stack,
260 library: 'animation library',
261 context: ErrorDescription('while notifying status listeners for $runtimeType'),
262 informationCollector: collector,
263 ),
264 );
265 }
266 }
267 }
268}
269