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 'dart:ui' show VoidCallback;
6
7import 'package:meta/meta.dart';
8
9import 'assertions.dart';
10import 'diagnostics.dart';
11import 'memory_allocations.dart';
12
13export '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.
57abstract 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.
89abstract 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
99const 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.
134mixin 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
491class _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.
534class 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