import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// A duplicate of the ChangeNotifier class which has the dispose method /// renamed to disposeListeners. This is done so that it can be used /// as a mixin with a State class which also has a dispose method class DisposableChangeNotifier implements Listenable { ObserverList _listeners = ObserverList(); 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(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()); _listeners.remove(listener); } /// 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 disposeListenables() { assert(_debugAssertNotDisposed()); _listeners = null; } /// Call all the registered listeners. /// /// Call this method whenever the object changes, to notify any clients the /// object may have. 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 != null) { final List localListeners = List.from(_listeners); for (final VoidCallback listener in localListeners) { try { if (_listeners.contains(listener)) { 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( 'The $runtimeType sending notification was', this, style: DiagnosticsTreeStyle.errorProperty, ); }, )); } } } } }