// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:ui' show VoidCallback; import 'package:meta/meta.dart'; import 'assertions.dart'; import 'diagnostics.dart'; import 'memory_allocations.dart'; export 'dart:ui' show VoidCallback; /// An object that maintains a list of listeners. /// /// The listeners are typically used to notify clients that the object has been /// updated. /// /// There are two variants of this interface: /// /// * [ValueListenable], an interface that augments the [Listenable] interface /// with the concept of a _current value_. /// /// * [Animation], an interface that augments the [ValueListenable] interface /// to add the concept of direction (forward or reverse). /// /// Many classes in the Flutter API use or implement these interfaces. The /// following subclasses are especially relevant: /// /// * [ChangeNotifier], which can be subclassed or mixed in to create objects /// that implement the [Listenable] interface. /// /// * [ValueNotifier], which implements the [ValueListenable] interface with /// a mutable value that triggers the notifications when modified. /// /// The terms "notify clients", "send notifications", "trigger notifications", /// and "fire notifications" are used interchangeably. /// /// See also: /// /// * [AnimatedBuilder], a widget that uses a builder callback to rebuild /// whenever a given [Listenable] triggers its notifications. This widget is /// commonly used with [Animation] subclasses, hence its name, but is by no /// means limited to animations, as it can be used with any [Listenable]. It /// is a subclass of [AnimatedWidget], which can be used to create widgets /// that are driven from a [Listenable]. /// * [ValueListenableBuilder], a widget that uses a builder callback to /// rebuild whenever a [ValueListenable] object triggers its notifications, /// providing the builder with the value of the object. /// * [InheritedNotifier], an abstract superclass for widgets that use a /// [Listenable]'s notifications to trigger rebuilds in descendant widgets /// that declare a dependency on them, using the [InheritedWidget] mechanism. /// * [Listenable.merge], which creates a [Listenable] that triggers /// notifications whenever any of a list of other [Listenable]s trigger their /// notifications. abstract class Listenable { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. const Listenable(); /// Return a [Listenable] that triggers when any of the given [Listenable]s /// themselves trigger. /// /// The list must not be changed after this method has been called. Doing so /// will lead to memory leaks or exceptions. /// /// The list may contain nulls; they are ignored. factory Listenable.merge(List<Listenable?> listenables) = _MergingListenable; /// Register a closure to be called when the object notifies its listeners. void addListener(VoidCallback listener); /// Remove a previously registered closure from the list of closures that the /// object notifies. void removeListener(VoidCallback listener); } /// An interface for subclasses of [Listenable] that expose a [value]. /// /// This interface is implemented by [ValueNotifier<T>] and [Animation<T>], and /// allows other APIs to accept either of those implementations interchangeably. /// /// See also: /// /// * [ValueListenableBuilder], a widget that uses a builder callback to /// rebuild whenever a [ValueListenable] object triggers its notifications, /// providing the builder with the value of the object. abstract class ValueListenable<T> extends Listenable { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. const ValueListenable(); /// The current value of the object. When the value changes, the callbacks /// registered with [addListener] will be invoked. T get value; } const String _flutterFoundationLibrary = 'package:flutter/foundation.dart'; /// A class that can be extended or mixed in that provides a change notification /// API using [VoidCallback] for notifications. /// /// It is O(1) for adding listeners and O(N) for removing listeners and dispatching /// notifications (where N is the number of listeners). /// /// {@macro flutter.flutter.animatedbuilder_changenotifier.rebuild} /// /// See also: /// /// * [ValueNotifier], which is a [ChangeNotifier] that wraps a single value. class ChangeNotifier implements Listenable { int _count = 0; // The _listeners is intentionally set to a fixed-length _GrowableList instead // of const []. // // The const [] creates an instance of _ImmutableList which would be // different from fixed-length _GrowableList used elsewhere in this class. // keeping runtime type the same during the lifetime of this class lets the // compiler to infer concrete type for this property, and thus improves // performance. static final List<VoidCallback?> _emptyListeners = List<VoidCallback?>.filled(0, null); List<VoidCallback?> _listeners = _emptyListeners; int _notificationCallStackDepth = 0; int _reentrantlyRemovedListeners = 0; bool _debugDisposed = false; /// If true, the event [ObjectCreated] for this instance was dispatched to /// [MemoryAllocations]. /// /// As [ChangedNotifier] is used as mixin, it does not have constructor, /// so we use [addListener] to dispatch the event. bool _creationDispatched = false; /// Used by subclasses to assert that the [ChangeNotifier] has not yet been /// disposed. /// /// {@tool snippet} /// The [debugAssertNotDisposed] function should only be called inside of an /// assert, as in this example. /// /// ```dart /// class MyNotifier with ChangeNotifier { /// void doUpdate() { /// assert(ChangeNotifier.debugAssertNotDisposed(this)); /// // ... /// } /// } /// ``` /// {@end-tool} // This is static and not an instance method because too many people try to // implement ChangeNotifier instead of extending it (and so it is too breaking // to add a method, especially for debug). static bool debugAssertNotDisposed(ChangeNotifier notifier) { assert(() { if (notifier._debugDisposed) { throw FlutterError( 'A ${notifier.runtimeType} was used after being disposed.\n' 'Once you have called dispose() on a ${notifier.runtimeType}, it ' 'can no longer be used.', ); } return true; }()); return true; } /// Whether any listeners are currently registered. /// /// Clients should not depend on this value for their behavior, because having /// one listener's logic change when another listener happens to start or stop /// listening will lead to extremely hard-to-track bugs. Subclasses might use /// this information to determine whether to do any work when there are no /// listeners, however; for example, resuming a [Stream] when a listener is /// added and pausing it when a listener is removed. /// /// Typically this is used by overriding [addListener], checking if /// [hasListeners] is false before calling `super.addListener()`, and if so, /// starting whatever work is needed to determine when to call /// [notifyListeners]; and similarly, by overriding [removeListener], checking /// if [hasListeners] is false after calling `super.removeListener()`, and if /// so, stopping that same work. /// /// This method returns false if [dispose] has been called. @protected bool get hasListeners => _count > 0; /// Register a closure to be called when the object changes. /// /// If the given closure is already registered, an additional instance is /// added, and must be removed the same number of times it is added before it /// will stop being called. /// /// This method must not be called after [dispose] has been called. /// /// {@template flutter.foundation.ChangeNotifier.addListener} /// If a listener is added twice, and is removed once during an iteration /// (e.g. in response to a notification), it will still be called again. If, /// on the other hand, it is removed as many times as it was registered, then /// it will no longer be called. This odd behavior is the result of the /// [ChangeNotifier] not being able to determine which listener is being /// removed, since they are identical, therefore it will conservatively still /// call all the listeners when it knows that any are still registered. /// /// This surprising behavior can be unexpectedly observed when registering a /// listener on two separate objects which are both forwarding all /// registrations to a common upstream object. /// {@endtemplate} /// /// See also: /// /// * [removeListener], which removes a previously registered closure from /// the list of closures that are notified when the object changes. @override void addListener(VoidCallback listener) { assert(ChangeNotifier.debugAssertNotDisposed(this)); if (kFlutterMemoryAllocationsEnabled && !_creationDispatched) { MemoryAllocations.instance.dispatchObjectEvent(ObjectCreated( library: _flutterFoundationLibrary, className: 'ChangeNotifier', object: this, )); _creationDispatched = true; } if (_count == _listeners.length) { if (_count == 0) { _listeners = List<VoidCallback?>.filled(1, null); } else { final List<VoidCallback?> newListeners = List<VoidCallback?>.filled(_listeners.length * 2, null); for (int i = 0; i < _count; i++) { newListeners[i] = _listeners[i]; } _listeners = newListeners; } } _listeners[_count++] = listener; } void _removeAt(int index) { // The list holding the listeners is not growable for performances reasons. // We still want to shrink this list if a lot of listeners have been added // and then removed outside a notifyListeners iteration. // We do this only when the real number of listeners is half the length // of our list. _count -= 1; if (_count * 2 <= _listeners.length) { final List<VoidCallback?> newListeners = List<VoidCallback?>.filled(_count, null); // Listeners before the index are at the same place. for (int i = 0; i < index; i++) { newListeners[i] = _listeners[i]; } // Listeners after the index move towards the start of the list. for (int i = index; i < _count; i++) { newListeners[i] = _listeners[i + 1]; } _listeners = newListeners; } else { // When there are more listeners than half the length of the list, we only // shift our listeners, so that we avoid to reallocate memory for the // whole list. for (int i = index; i < _count; i++) { _listeners[i] = _listeners[i + 1]; } _listeners[_count] = null; } } /// Remove a previously registered closure from the list of closures that are /// notified when the object changes. /// /// If the given listener is not registered, the call is ignored. /// /// This method returns immediately if [dispose] has been called. /// /// {@macro flutter.foundation.ChangeNotifier.addListener} /// /// See also: /// /// * [addListener], which registers a closure to be called when the object /// changes. @override void removeListener(VoidCallback listener) { // This method is allowed to be called on disposed instances for usability // reasons. Due to how our frame scheduling logic between render objects and // overlays, it is common that the owner of this instance would be disposed a // frame earlier than the listeners. Allowing calls to this method after it // is disposed makes it easier for listeners to properly clean up. for (int i = 0; i < _count; i++) { final VoidCallback? listenerAtIndex = _listeners[i]; if (listenerAtIndex == listener) { if (_notificationCallStackDepth > 0) { // We don't resize the list during notifyListeners iterations // but we set to null, the listeners we want to remove. We will // effectively resize the list at the end of all notifyListeners // iterations. _listeners[i] = null; _reentrantlyRemovedListeners++; } else { // When we are outside the notifyListeners iterations we can // effectively shrink the list. _removeAt(i); } break; } } } /// Discards any resources used by the object. After this is called, the /// object is not in a usable state and should be discarded (calls to /// [addListener] will throw after the object is disposed). /// /// This method should only be called by the object's owner. /// /// This method does not notify listeners, and clears the listener list once /// it is called. Consumers of this class must decide on whether to notify /// listeners or not immediately before disposal. @mustCallSuper void dispose() { assert(ChangeNotifier.debugAssertNotDisposed(this)); assert(() { _debugDisposed = true; return true; }()); if (kFlutterMemoryAllocationsEnabled && _creationDispatched) { MemoryAllocations.instance.dispatchObjectEvent(ObjectDisposed(object: this)); } _listeners = _emptyListeners; _count = 0; } /// Call all the registered listeners. /// /// Call this method whenever the object changes, to notify any clients the /// object may have changed. Listeners that are added during this iteration /// will not be visited. Listeners that are removed during this iteration will /// not be visited after they are removed. /// /// Exceptions thrown by listeners will be caught and reported using /// [FlutterError.reportError]. /// /// This method must not be called after [dispose] has been called. /// /// Surprising behavior can result when reentrantly removing a listener (e.g. /// in response to a notification) that has been registered multiple times. /// See the discussion at [removeListener]. @protected @visibleForTesting @pragma('vm:notify-debugger-on-exception') void notifyListeners() { assert(ChangeNotifier.debugAssertNotDisposed(this)); if (_count == 0) { return; } // To make sure that listeners removed during this iteration are not called, // we set them to null, but we don't shrink the list right away. // By doing this, we can continue to iterate on our list until it reaches // the last listener added before the call to this method. // To allow potential listeners to recursively call notifyListener, we track // the number of times this method is called in _notificationCallStackDepth. // Once every recursive iteration is finished (i.e. when _notificationCallStackDepth == 0), // we can safely shrink our list so that it will only contain not null // listeners. _notificationCallStackDepth++; final int end = _count; for (int i = 0; i < end; i++) { try { _listeners[i]?.call(); } catch (exception, stack) { FlutterError.reportError(FlutterErrorDetails( exception: exception, stack: stack, library: 'foundation library', context: ErrorDescription('while dispatching notifications for $runtimeType'), informationCollector: () => <DiagnosticsNode>[ DiagnosticsProperty<ChangeNotifier>( 'The $runtimeType sending notification was', this, style: DiagnosticsTreeStyle.errorProperty, ), ], )); } } _notificationCallStackDepth--; if (_notificationCallStackDepth == 0 && _reentrantlyRemovedListeners > 0) { // We really remove the listeners when all notifications are done. final int newLength = _count - _reentrantlyRemovedListeners; if (newLength * 2 <= _listeners.length) { // As in _removeAt, we only shrink the list when the real number of // listeners is half the length of our list. final List<VoidCallback?> newListeners = List<VoidCallback?>.filled(newLength, null); int newIndex = 0; for (int i = 0; i < _count; i++) { final VoidCallback? listener = _listeners[i]; if (listener != null) { newListeners[newIndex++] = listener; } } _listeners = newListeners; } else { // Otherwise we put all the null references at the end. for (int i = 0; i < newLength; i += 1) { if (_listeners[i] == null) { // We swap this item with the next not null item. int swapIndex = i + 1; while(_listeners[swapIndex] == null) { swapIndex += 1; } _listeners[i] = _listeners[swapIndex]; _listeners[swapIndex] = null; } } } _reentrantlyRemovedListeners = 0; _count = newLength; } } } class _MergingListenable extends Listenable { _MergingListenable(this._children); final List<Listenable?> _children; @override void addListener(VoidCallback listener) { for (final Listenable? child in _children) { child?.addListener(listener); } } @override void removeListener(VoidCallback listener) { for (final Listenable? child in _children) { child?.removeListener(listener); } } @override String toString() { return 'Listenable.merge([${_children.join(", ")}])'; } } /// A [ChangeNotifier] that holds a single value. /// /// When [value] is replaced with something that is not equal to the old /// value as evaluated by the equality operator ==, this class notifies its /// listeners. class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> { /// Creates a [ChangeNotifier] that wraps this value. ValueNotifier(this._value) { if (kFlutterMemoryAllocationsEnabled) { MemoryAllocations.instance.dispatchObjectEvent(ObjectCreated( library: _flutterFoundationLibrary, className: 'ValueNotifier', object: this, )); } _creationDispatched = true; } /// The current value stored in this notifier. /// /// When the value is replaced with something that is not equal to the old /// value as evaluated by the equality operator ==, this class notifies its /// listeners. @override T get value => _value; T _value; set value(T newValue) { if (_value == newValue) { return; } _value = newValue; notifyListeners(); } @override String toString() => '${describeIdentity(this)}($value)'; }