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 | import 'dart:ui' show VoidCallback; |
6 | |
7 | import 'package:meta/meta.dart' ; |
8 | |
9 | import 'assertions.dart'; |
10 | import 'diagnostics.dart'; |
11 | import 'memory_allocations.dart'; |
12 | |
13 | export 'dart:ui' show VoidCallback; |
14 | |
15 | /// An object that maintains a list of listeners. |
16 | /// |
17 | /// The listeners are typically used to notify clients that the object has been |
18 | /// updated. |
19 | /// |
20 | /// There are two variants of this interface: |
21 | /// |
22 | /// * [ValueListenable], an interface that augments the [Listenable] interface |
23 | /// with the concept of a _current value_. |
24 | /// |
25 | /// * [Animation], an interface that augments the [ValueListenable] interface |
26 | /// to add the concept of direction (forward or reverse). |
27 | /// |
28 | /// Many classes in the Flutter API use or implement these interfaces. The |
29 | /// following subclasses are especially relevant: |
30 | /// |
31 | /// * [ChangeNotifier], which can be subclassed or mixed in to create objects |
32 | /// that implement the [Listenable] interface. |
33 | /// |
34 | /// * [ValueNotifier], which implements the [ValueListenable] interface with |
35 | /// a mutable value that triggers the notifications when modified. |
36 | /// |
37 | /// The terms "notify clients", "send notifications", "trigger notifications", |
38 | /// and "fire notifications" are used interchangeably. |
39 | /// |
40 | /// See also: |
41 | /// |
42 | /// * [AnimatedBuilder], a widget that uses a builder callback to rebuild |
43 | /// whenever a given [Listenable] triggers its notifications. This widget is |
44 | /// commonly used with [Animation] subclasses, hence its name, but is by no |
45 | /// means limited to animations, as it can be used with any [Listenable]. It |
46 | /// is a subclass of [AnimatedWidget], which can be used to create widgets |
47 | /// that are driven from a [Listenable]. |
48 | /// * [ValueListenableBuilder], a widget that uses a builder callback to |
49 | /// rebuild whenever a [ValueListenable] object triggers its notifications, |
50 | /// providing the builder with the value of the object. |
51 | /// * [InheritedNotifier], an abstract superclass for widgets that use a |
52 | /// [Listenable]'s notifications to trigger rebuilds in descendant widgets |
53 | /// that declare a dependency on them, using the [InheritedWidget] mechanism. |
54 | /// * [Listenable.merge], which creates a [Listenable] that triggers |
55 | /// notifications whenever any of a list of other [Listenable]s trigger their |
56 | /// notifications. |
57 | abstract class Listenable { |
58 | /// Abstract const constructor. This constructor enables subclasses to provide |
59 | /// const constructors so that they can be used in const expressions. |
60 | const Listenable(); |
61 | |
62 | /// Return a [Listenable] that triggers when any of the given [Listenable]s |
63 | /// themselves trigger. |
64 | /// |
65 | /// The list must not be changed after this method has been called. Doing so |
66 | /// will lead to memory leaks or exceptions. |
67 | /// |
68 | /// The list may contain nulls; they are ignored. |
69 | factory Listenable.merge(List<Listenable?> listenables) = _MergingListenable; |
70 | |
71 | /// Register a closure to be called when the object notifies its listeners. |
72 | void addListener(VoidCallback listener); |
73 | |
74 | /// Remove a previously registered closure from the list of closures that the |
75 | /// object notifies. |
76 | void removeListener(VoidCallback listener); |
77 | } |
78 | |
79 | /// An interface for subclasses of [Listenable] that expose a [value]. |
80 | /// |
81 | /// This interface is implemented by [ValueNotifier<T>] and [Animation<T>], and |
82 | /// allows other APIs to accept either of those implementations interchangeably. |
83 | /// |
84 | /// See also: |
85 | /// |
86 | /// * [ValueListenableBuilder], a widget that uses a builder callback to |
87 | /// rebuild whenever a [ValueListenable] object triggers its notifications, |
88 | /// providing the builder with the value of the object. |
89 | abstract class ValueListenable<T> extends Listenable { |
90 | /// Abstract const constructor. This constructor enables subclasses to provide |
91 | /// const constructors so that they can be used in const expressions. |
92 | const ValueListenable(); |
93 | |
94 | /// The current value of the object. When the value changes, the callbacks |
95 | /// registered with [addListener] will be invoked. |
96 | T get value; |
97 | } |
98 | |
99 | const String _flutterFoundationLibrary = 'package:flutter/foundation.dart' ; |
100 | |
101 | /// A class that can be extended or mixed in that provides a change notification |
102 | /// API using [VoidCallback] for notifications. |
103 | /// |
104 | /// It is O(1) for adding listeners and O(N) for removing listeners and dispatching |
105 | /// notifications (where N is the number of listeners). |
106 | /// |
107 | /// ## Using ChangeNotifier subclasses for data models |
108 | /// |
109 | /// A data structure can extend or mix in [ChangeNotifier] to implement the |
110 | /// [Listenable] interface and thus become usable with widgets that listen for |
111 | /// changes to [Listenable]s, such as [ListenableBuilder]. |
112 | /// |
113 | /// {@tool dartpad} |
114 | /// The following example implements a simple counter that utilizes a |
115 | /// [ListenableBuilder] to limit rebuilds to only the [Text] widget containing |
116 | /// the count. The current count is stored in a [ChangeNotifier] subclass, which |
117 | /// rebuilds the [ListenableBuilder]'s contents when its value is changed. |
118 | /// |
119 | /// ** See code in examples/api/lib/widgets/transitions/listenable_builder.2.dart ** |
120 | /// {@end-tool} |
121 | /// |
122 | /// {@tool dartpad} |
123 | /// In this case, the [ChangeNotifier] subclass encapsulates a list, and notifies |
124 | /// the clients any time an item is added to the list. This example only supports |
125 | /// adding items; as an exercise, consider adding buttons to remove items from |
126 | /// the list as well. |
127 | /// |
128 | /// ** See code in examples/api/lib/widgets/transitions/listenable_builder.3.dart ** |
129 | /// {@end-tool} |
130 | /// |
131 | /// See also: |
132 | /// |
133 | /// * [ValueNotifier], which is a [ChangeNotifier] that wraps a single value. |
134 | mixin class ChangeNotifier implements Listenable { |
135 | int _count = 0; |
136 | // The _listeners is intentionally set to a fixed-length _GrowableList instead |
137 | // of const []. |
138 | // |
139 | // The const [] creates an instance of _ImmutableList which would be |
140 | // different from fixed-length _GrowableList used elsewhere in this class. |
141 | // keeping runtime type the same during the lifetime of this class lets the |
142 | // compiler to infer concrete type for this property, and thus improves |
143 | // performance. |
144 | static final List<VoidCallback?> _emptyListeners = List<VoidCallback?>.filled(0, null); |
145 | List<VoidCallback?> _listeners = _emptyListeners; |
146 | int _notificationCallStackDepth = 0; |
147 | int _reentrantlyRemovedListeners = 0; |
148 | bool _debugDisposed = false; |
149 | |
150 | /// If true, the event [ObjectCreated] for this instance was dispatched to |
151 | /// [FlutterMemoryAllocations]. |
152 | /// |
153 | /// As [ChangedNotifier] is used as mixin, it does not have constructor, |
154 | /// so we use [addListener] to dispatch the event. |
155 | bool _creationDispatched = false; |
156 | |
157 | /// Used by subclasses to assert that the [ChangeNotifier] has not yet been |
158 | /// disposed. |
159 | /// |
160 | /// {@tool snippet} |
161 | /// The [debugAssertNotDisposed] function should only be called inside of an |
162 | /// assert, as in this example. |
163 | /// |
164 | /// ```dart |
165 | /// class MyNotifier with ChangeNotifier { |
166 | /// void doUpdate() { |
167 | /// assert(ChangeNotifier.debugAssertNotDisposed(this)); |
168 | /// // ... |
169 | /// } |
170 | /// } |
171 | /// ``` |
172 | /// {@end-tool} |
173 | // This is static and not an instance method because too many people try to |
174 | // implement ChangeNotifier instead of extending it (and so it is too breaking |
175 | // to add a method, especially for debug). |
176 | static bool debugAssertNotDisposed(ChangeNotifier notifier) { |
177 | assert(() { |
178 | if (notifier._debugDisposed) { |
179 | throw FlutterError( |
180 | 'A ${notifier.runtimeType} was used after being disposed.\n' |
181 | 'Once you have called dispose() on a ${notifier.runtimeType}, it ' |
182 | 'can no longer be used.' , |
183 | ); |
184 | } |
185 | return true; |
186 | }()); |
187 | return true; |
188 | } |
189 | |
190 | /// Whether any listeners are currently registered. |
191 | /// |
192 | /// Clients should not depend on this value for their behavior, because having |
193 | /// one listener's logic change when another listener happens to start or stop |
194 | /// listening will lead to extremely hard-to-track bugs. Subclasses might use |
195 | /// this information to determine whether to do any work when there are no |
196 | /// listeners, however; for example, resuming a [Stream] when a listener is |
197 | /// added and pausing it when a listener is removed. |
198 | /// |
199 | /// Typically this is used by overriding [addListener], checking if |
200 | /// [hasListeners] is false before calling `super.addListener()`, and if so, |
201 | /// starting whatever work is needed to determine when to call |
202 | /// [notifyListeners]; and similarly, by overriding [removeListener], checking |
203 | /// if [hasListeners] is false after calling `super.removeListener()`, and if |
204 | /// so, stopping that same work. |
205 | /// |
206 | /// This method returns false if [dispose] has been called. |
207 | @protected |
208 | bool get hasListeners => _count > 0; |
209 | |
210 | /// Dispatches event of the [object] creation to [FlutterMemoryAllocations.instance]. |
211 | /// |
212 | /// If the event was already dispatched or [kFlutterMemoryAllocationsEnabled] |
213 | /// is false, the method is noop. |
214 | /// |
215 | /// Tools like leak_tracker use the event of object creation to help |
216 | /// developers identify the owner of the object, for troubleshooting purposes, |
217 | /// by taking stack trace at the moment of the event. |
218 | /// |
219 | /// But, as [ChangeNotifier] is mixin, it does not have its own constructor. So, it |
220 | /// communicates object creation in first `addListener`, that results |
221 | /// in the stack trace pointing to `addListener`, not to constructor. |
222 | /// |
223 | /// To make debugging easier, invoke [ChangeNotifier.maybeDispatchObjectCreation] |
224 | /// in constructor of the class. It will help |
225 | /// to identify the owner. |
226 | /// |
227 | /// Make sure to invoke it with condition `if (kFlutterMemoryAllocationsEnabled) ...` |
228 | /// so that the method is tree-shaken away when the flag is false. |
229 | @protected |
230 | static void maybeDispatchObjectCreation(ChangeNotifier object) { |
231 | // Tree shaker does not include this method and the class MemoryAllocations |
232 | // if kFlutterMemoryAllocationsEnabled is false. |
233 | if (kFlutterMemoryAllocationsEnabled && !object._creationDispatched) { |
234 | FlutterMemoryAllocations.instance.dispatchObjectCreated( |
235 | library: _flutterFoundationLibrary, |
236 | className: ' $ChangeNotifier' , |
237 | object: object, |
238 | ); |
239 | object._creationDispatched = true; |
240 | } |
241 | } |
242 | |
243 | /// Register a closure to be called when the object changes. |
244 | /// |
245 | /// If the given closure is already registered, an additional instance is |
246 | /// added, and must be removed the same number of times it is added before it |
247 | /// will stop being called. |
248 | /// |
249 | /// This method must not be called after [dispose] has been called. |
250 | /// |
251 | /// {@template flutter.foundation.ChangeNotifier.addListener} |
252 | /// If a listener is added twice, and is removed once during an iteration |
253 | /// (e.g. in response to a notification), it will still be called again. If, |
254 | /// on the other hand, it is removed as many times as it was registered, then |
255 | /// it will no longer be called. This odd behavior is the result of the |
256 | /// [ChangeNotifier] not being able to determine which listener is being |
257 | /// removed, since they are identical, therefore it will conservatively still |
258 | /// call all the listeners when it knows that any are still registered. |
259 | /// |
260 | /// This surprising behavior can be unexpectedly observed when registering a |
261 | /// listener on two separate objects which are both forwarding all |
262 | /// registrations to a common upstream object. |
263 | /// {@endtemplate} |
264 | /// |
265 | /// See also: |
266 | /// |
267 | /// * [removeListener], which removes a previously registered closure from |
268 | /// the list of closures that are notified when the object changes. |
269 | @override |
270 | void addListener(VoidCallback listener) { |
271 | assert(ChangeNotifier.debugAssertNotDisposed(this)); |
272 | |
273 | if (kFlutterMemoryAllocationsEnabled) { |
274 | maybeDispatchObjectCreation(this); |
275 | } |
276 | |
277 | if (_count == _listeners.length) { |
278 | if (_count == 0) { |
279 | _listeners = List<VoidCallback?>.filled(1, null); |
280 | } else { |
281 | final List<VoidCallback?> newListeners = |
282 | List<VoidCallback?>.filled(_listeners.length * 2, null); |
283 | for (int i = 0; i < _count; i++) { |
284 | newListeners[i] = _listeners[i]; |
285 | } |
286 | _listeners = newListeners; |
287 | } |
288 | } |
289 | _listeners[_count++] = listener; |
290 | } |
291 | |
292 | void _removeAt(int index) { |
293 | // The list holding the listeners is not growable for performances reasons. |
294 | // We still want to shrink this list if a lot of listeners have been added |
295 | // and then removed outside a notifyListeners iteration. |
296 | // We do this only when the real number of listeners is half the length |
297 | // of our list. |
298 | _count -= 1; |
299 | if (_count * 2 <= _listeners.length) { |
300 | final List<VoidCallback?> newListeners = List<VoidCallback?>.filled(_count, null); |
301 | |
302 | // Listeners before the index are at the same place. |
303 | for (int i = 0; i < index; i++) { |
304 | newListeners[i] = _listeners[i]; |
305 | } |
306 | |
307 | // Listeners after the index move towards the start of the list. |
308 | for (int i = index; i < _count; i++) { |
309 | newListeners[i] = _listeners[i + 1]; |
310 | } |
311 | |
312 | _listeners = newListeners; |
313 | } else { |
314 | // When there are more listeners than half the length of the list, we only |
315 | // shift our listeners, so that we avoid to reallocate memory for the |
316 | // whole list. |
317 | for (int i = index; i < _count; i++) { |
318 | _listeners[i] = _listeners[i + 1]; |
319 | } |
320 | _listeners[_count] = null; |
321 | } |
322 | } |
323 | |
324 | /// Remove a previously registered closure from the list of closures that are |
325 | /// notified when the object changes. |
326 | /// |
327 | /// If the given listener is not registered, the call is ignored. |
328 | /// |
329 | /// This method returns immediately if [dispose] has been called. |
330 | /// |
331 | /// {@macro flutter.foundation.ChangeNotifier.addListener} |
332 | /// |
333 | /// See also: |
334 | /// |
335 | /// * [addListener], which registers a closure to be called when the object |
336 | /// changes. |
337 | @override |
338 | void removeListener(VoidCallback listener) { |
339 | // This method is allowed to be called on disposed instances for usability |
340 | // reasons. Due to how our frame scheduling logic between render objects and |
341 | // overlays, it is common that the owner of this instance would be disposed a |
342 | // frame earlier than the listeners. Allowing calls to this method after it |
343 | // is disposed makes it easier for listeners to properly clean up. |
344 | for (int i = 0; i < _count; i++) { |
345 | final VoidCallback? listenerAtIndex = _listeners[i]; |
346 | if (listenerAtIndex == listener) { |
347 | if (_notificationCallStackDepth > 0) { |
348 | // We don't resize the list during notifyListeners iterations |
349 | // but we set to null, the listeners we want to remove. We will |
350 | // effectively resize the list at the end of all notifyListeners |
351 | // iterations. |
352 | _listeners[i] = null; |
353 | _reentrantlyRemovedListeners++; |
354 | } else { |
355 | // When we are outside the notifyListeners iterations we can |
356 | // effectively shrink the list. |
357 | _removeAt(i); |
358 | } |
359 | break; |
360 | } |
361 | } |
362 | } |
363 | |
364 | /// Discards any resources used by the object. After this is called, the |
365 | /// object is not in a usable state and should be discarded (calls to |
366 | /// [addListener] will throw after the object is disposed). |
367 | /// |
368 | /// This method should only be called by the object's owner. |
369 | /// |
370 | /// This method does not notify listeners, and clears the listener list once |
371 | /// it is called. Consumers of this class must decide on whether to notify |
372 | /// listeners or not immediately before disposal. |
373 | @mustCallSuper |
374 | void dispose() { |
375 | assert(ChangeNotifier.debugAssertNotDisposed(this)); |
376 | assert( |
377 | _notificationCallStackDepth == 0, |
378 | 'The "dispose()" method on $this was called during the call to ' |
379 | '"notifyListeners()". This is likely to cause errors since it modifies ' |
380 | 'the list of listeners while the list is being used.' , |
381 | ); |
382 | assert(() { |
383 | _debugDisposed = true; |
384 | return true; |
385 | }()); |
386 | if (kFlutterMemoryAllocationsEnabled && _creationDispatched) { |
387 | FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this); |
388 | } |
389 | _listeners = _emptyListeners; |
390 | _count = 0; |
391 | } |
392 | |
393 | /// Call all the registered listeners. |
394 | /// |
395 | /// Call this method whenever the object changes, to notify any clients the |
396 | /// object may have changed. Listeners that are added during this iteration |
397 | /// will not be visited. Listeners that are removed during this iteration will |
398 | /// not be visited after they are removed. |
399 | /// |
400 | /// Exceptions thrown by listeners will be caught and reported using |
401 | /// [FlutterError.reportError]. |
402 | /// |
403 | /// This method must not be called after [dispose] has been called. |
404 | /// |
405 | /// Surprising behavior can result when reentrantly removing a listener (e.g. |
406 | /// in response to a notification) that has been registered multiple times. |
407 | /// See the discussion at [removeListener]. |
408 | @protected |
409 | @visibleForTesting |
410 | @pragma('vm:notify-debugger-on-exception' ) |
411 | void notifyListeners() { |
412 | assert(ChangeNotifier.debugAssertNotDisposed(this)); |
413 | if (_count == 0) { |
414 | return; |
415 | } |
416 | |
417 | // To make sure that listeners removed during this iteration are not called, |
418 | // we set them to null, but we don't shrink the list right away. |
419 | // By doing this, we can continue to iterate on our list until it reaches |
420 | // the last listener added before the call to this method. |
421 | |
422 | // To allow potential listeners to recursively call notifyListener, we track |
423 | // the number of times this method is called in _notificationCallStackDepth. |
424 | // Once every recursive iteration is finished (i.e. when _notificationCallStackDepth == 0), |
425 | // we can safely shrink our list so that it will only contain not null |
426 | // listeners. |
427 | |
428 | _notificationCallStackDepth++; |
429 | |
430 | final int end = _count; |
431 | for (int i = 0; i < end; i++) { |
432 | try { |
433 | _listeners[i]?.call(); |
434 | } catch (exception, stack) { |
435 | FlutterError.reportError(FlutterErrorDetails( |
436 | exception: exception, |
437 | stack: stack, |
438 | library: 'foundation library' , |
439 | context: ErrorDescription('while dispatching notifications for $runtimeType' ), |
440 | informationCollector: () => <DiagnosticsNode>[ |
441 | DiagnosticsProperty<ChangeNotifier>( |
442 | 'The $runtimeType sending notification was' , |
443 | this, |
444 | style: DiagnosticsTreeStyle.errorProperty, |
445 | ), |
446 | ], |
447 | )); |
448 | } |
449 | } |
450 | |
451 | _notificationCallStackDepth--; |
452 | |
453 | if (_notificationCallStackDepth == 0 && _reentrantlyRemovedListeners > 0) { |
454 | // We really remove the listeners when all notifications are done. |
455 | final int newLength = _count - _reentrantlyRemovedListeners; |
456 | if (newLength * 2 <= _listeners.length) { |
457 | // As in _removeAt, we only shrink the list when the real number of |
458 | // listeners is half the length of our list. |
459 | final List<VoidCallback?> newListeners = List<VoidCallback?>.filled(newLength, null); |
460 | |
461 | int newIndex = 0; |
462 | for (int i = 0; i < _count; i++) { |
463 | final VoidCallback? listener = _listeners[i]; |
464 | if (listener != null) { |
465 | newListeners[newIndex++] = listener; |
466 | } |
467 | } |
468 | |
469 | _listeners = newListeners; |
470 | } else { |
471 | // Otherwise we put all the null references at the end. |
472 | for (int i = 0; i < newLength; i += 1) { |
473 | if (_listeners[i] == null) { |
474 | // We swap this item with the next not null item. |
475 | int swapIndex = i + 1; |
476 | while (_listeners[swapIndex] == null) { |
477 | swapIndex += 1; |
478 | } |
479 | _listeners[i] = _listeners[swapIndex]; |
480 | _listeners[swapIndex] = null; |
481 | } |
482 | } |
483 | } |
484 | |
485 | _reentrantlyRemovedListeners = 0; |
486 | _count = newLength; |
487 | } |
488 | } |
489 | } |
490 | |
491 | class _MergingListenable extends Listenable { |
492 | _MergingListenable(this._children); |
493 | |
494 | final List<Listenable?> _children; |
495 | |
496 | @override |
497 | void addListener(VoidCallback listener) { |
498 | for (final Listenable? child in _children) { |
499 | child?.addListener(listener); |
500 | } |
501 | } |
502 | |
503 | @override |
504 | void removeListener(VoidCallback listener) { |
505 | for (final Listenable? child in _children) { |
506 | child?.removeListener(listener); |
507 | } |
508 | } |
509 | |
510 | @override |
511 | String toString() { |
512 | return 'Listenable.merge([ ${_children.join(", " )}])' ; |
513 | } |
514 | } |
515 | |
516 | /// A [ChangeNotifier] that holds a single value. |
517 | /// |
518 | /// When [value] is replaced with something that is not equal to the old |
519 | /// value as evaluated by the equality operator ==, this class notifies its |
520 | /// listeners. |
521 | /// |
522 | /// ## Limitations |
523 | /// |
524 | /// Because this class only notifies listeners when the [value]'s _identity_ |
525 | /// changes, listeners will not be notified when mutable state within the |
526 | /// value itself changes. |
527 | /// |
528 | /// For example, a `ValueNotifier<List<int>>` will not notify its listeners |
529 | /// when the _contents_ of the list are changed. |
530 | /// |
531 | /// As a result, this class is best used with only immutable data types. |
532 | /// |
533 | /// For mutable data types, consider extending [ChangeNotifier] directly. |
534 | class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> { |
535 | /// Creates a [ChangeNotifier] that wraps this value. |
536 | ValueNotifier(this._value) { |
537 | if (kFlutterMemoryAllocationsEnabled) { |
538 | ChangeNotifier.maybeDispatchObjectCreation(this); |
539 | } |
540 | } |
541 | |
542 | /// The current value stored in this notifier. |
543 | /// |
544 | /// When the value is replaced with something that is not equal to the old |
545 | /// value as evaluated by the equality operator ==, this class notifies its |
546 | /// listeners. |
547 | @override |
548 | T get value => _value; |
549 | T _value; |
550 | set value(T newValue) { |
551 | if (_value == newValue) { |
552 | return; |
553 | } |
554 | _value = newValue; |
555 | notifyListeners(); |
556 | } |
557 | |
558 | @override |
559 | String toString() => ' ${describeIdentity(this)}( $value)' ; |
560 | } |
561 | |