| // 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:collection'; |
| |
| import 'package:meta/meta.dart'; |
| |
| import 'assertions.dart'; |
| import 'basic_types.dart'; |
| import 'diagnostics.dart'; |
| |
| /// 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. |
| /// * [new 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; |
| } |
| |
| class _ListenerEntry extends LinkedListEntry<_ListenerEntry> { |
| _ListenerEntry(this.listener); |
| final VoidCallback listener; |
| } |
| |
| /// 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). |
| /// |
| /// See also: |
| /// |
| /// * [ValueNotifier], which is a [ChangeNotifier] that wraps a single value. |
| class ChangeNotifier implements Listenable { |
| LinkedList<_ListenerEntry>? _listeners = LinkedList<_ListenerEntry>(); |
| |
| bool _debugAssertNotDisposed() { |
| assert(() { |
| if (_listeners == null) { |
| throw FlutterError( |
| 'A $runtimeType was used after being disposed.\n' |
| 'Once you have called dispose() on a $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. |
| @protected |
| bool get hasListeners { |
| assert(_debugAssertNotDisposed()); |
| return _listeners!.isNotEmpty; |
| } |
| |
| /// Register a closure to be called when the object changes. |
| /// |
| /// This method must not be called after [dispose] has been called. |
| @override |
| void addListener(VoidCallback listener) { |
| assert(_debugAssertNotDisposed()); |
| _listeners!.add(_ListenerEntry(listener)); |
| } |
| |
| /// 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 must not be called after [dispose] has been called. |
| /// |
| /// If a listener had been added twice, and is removed once during an |
| /// iteration (i.e. 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, and therefore conservatively |
| /// still calling 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. |
| @override |
| void removeListener(VoidCallback listener) { |
| assert(_debugAssertNotDisposed()); |
| for (final _ListenerEntry entry in _listeners!) { |
| if (entry.listener == listener) { |
| entry.unlink(); |
| return; |
| } |
| } |
| } |
| |
| /// 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] and [removeListener] will throw after the object is |
| /// disposed). |
| /// |
| /// This method should only be called by the object's owner. |
| @mustCallSuper |
| void dispose() { |
| assert(_debugAssertNotDisposed()); |
| _listeners = null; |
| } |
| |
| /// 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 (i.e. |
| /// in response to a notification) that has been registered multiple times. |
| /// See the discussion at [removeListener]. |
| @protected |
| @visibleForTesting |
| void notifyListeners() { |
| assert(_debugAssertNotDisposed()); |
| if (_listeners!.isEmpty) |
| return; |
| |
| final List<_ListenerEntry> localListeners = List<_ListenerEntry>.from(_listeners!); |
| |
| for (final _ListenerEntry entry in localListeners) { |
| try { |
| if (entry.list != null) |
| entry.listener(); |
| } catch (exception, stack) { |
| FlutterError.reportError(FlutterErrorDetails( |
| exception: exception, |
| stack: stack, |
| library: 'foundation library', |
| context: ErrorDescription('while dispatching notifications for $runtimeType'), |
| informationCollector: () sync* { |
| yield DiagnosticsProperty<ChangeNotifier>( |
| 'The $runtimeType sending notification was', |
| this, |
| style: DiagnosticsTreeStyle.errorProperty, |
| ); |
| }, |
| )); |
| } |
| } |
| } |
| } |
| |
| 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); |
| |
| /// 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)'; |
| } |