| // 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 'package:engine/ui.dart' show VoidCallback; |
| |
| import 'package:meta/meta.dart'; |
| |
| import 'assertions.dart'; |
| import 'diagnostics.dart'; |
| import 'memory_allocations.dart'; |
| |
| export 'package:engine/ui.dart' 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:flute/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.ListenableBuilder.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.dispatchObjectCreated( |
| 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( |
| _notificationCallStackDepth == 0, |
| 'The "dispose()" method on $this was called during the call to ' |
| '"notifyListeners()". This is likely to cause errors since it modifies ' |
| 'the list of listeners while the list is being used.', |
| ); |
| assert(() { |
| _debugDisposed = true; |
| return true; |
| }()); |
| if (kFlutterMemoryAllocationsEnabled && _creationDispatched) { |
| MemoryAllocations.instance.dispatchObjectDisposed(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.dispatchObjectCreated( |
| 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)'; |
| } |