LCOV - code coverage report
Current view: top level - src - property_change_notifier.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 51 51 100.0 %
Date: 2019-12-19 10:35:55 Functions: 0 0 -

          Line data    Source code
       1             : import 'package:flutter/foundation.dart';
       2             : 
       3             : /// Signature of callbacks that have 1 argument and return no data.
       4             : typedef PropertyCallback<T> = void Function(T);
       5             : 
       6             : /// A backwards-compatible implementation of [ChangeNotifier] that allows
       7             : /// implementers to provide more granular information to listeners about what
       8             : /// specific property was changed. This lets listeners be much more efficient
       9             : /// when responding to model changes. Any number of listeners can subscribe to
      10             : /// any number of properties.
      11             : ///
      12             : /// Like [ChangeNotifier], is optimized for small numbers (one or two) of listeners.
      13             : /// It is O(N) for adding and removing listeners and O(N²) for dispatching
      14             : /// notifications (where N is the number of listeners).
      15             : ///
      16             : /// [T] is the type of the property name and is usually [String] but can
      17             : /// be an [Enum] or any type that subclasses [Object]. To work correctly,
      18             : /// [T] must implement `operator==` and `hashCode`.
      19             : class PropertyChangeNotifier<T extends Object> extends ChangeNotifier {
      20             :   var _globalListeners = ObserverList<Function>();
      21             :   var _propertyListeners = <T, ObserverList<Function>>{};
      22             : 
      23             :   /// Reimplemented from [ChangeNotifier].
      24             :   /// Clients should not depend on this value for their behavior, because having
      25             :   /// one listener's logic change when another listener happens to start or stop
      26             :   /// listening will lead to extremely hard-to-track bugs. Subclasses might use
      27             :   /// this information to determine whether to do any work when there are no
      28             :   /// listeners, however; for example, resuming a [Stream] when a listener is
      29             :   /// added and pausing it when a listener is removed.
      30             :   ///
      31             :   /// Typically this is used by overriding [addListener], checking if
      32             :   /// [hasListeners] is false before calling `super.addListener()`, and if so,
      33             :   /// starting whatever work is needed to determine when to call
      34             :   /// [notifyListeners]; and similarly, by overriding [removeListener], checking
      35             :   /// if [hasListeners] is false after calling `super.removeListener()`, and if
      36             :   /// so, stopping that same work.
      37           1 :   @override
      38             :   @protected
      39             :   @visibleForTesting
      40             :   bool get hasListeners {
      41           1 :     assert(_debugAssertNotDisposed());
      42           4 :     return _globalListeners.isNotEmpty || _propertyListeners.isNotEmpty;
      43             :   }
      44             : 
      45             :   /// Registers [listener] for the given [properties]. [listener] must not be null.
      46             :   /// If [properties] is null or empty, [listener] will be added as a global listener, meaning
      47             :   /// it will be invoked for all property changes. This is the default behavior of [ChangeNotifier].
      48             :   /// [listener] must either accept no parameters or a single [T] parameter. If [listener]
      49             :   /// accepts a [T] parameter, it will be invoked with the property name provided by [notifyListeners].
      50             :   /// The same [listener] can be added for multiple properties.
      51             :   /// Adding the same [listener] for the same property is a no-op.
      52             :   /// Adding a [listener] for a non-existent property will not fail, but is functionally pointless.
      53           3 :   @override
      54             :   void addListener(Function listener, [Iterable<T> properties]) {
      55           3 :     assert(_debugAssertNotDisposed());
      56           1 :     assert(listener != null);
      57           7 :     assert(listener is VoidCallback || listener is PropertyCallback<T>, 'Listener must be a Function() or Function(T)');
      58             : 
      59             :     // Register global listener only
      60           1 :     if (properties == null || properties.isEmpty) {
      61           6 :       _addListener(_globalListeners, listener);
      62             :       return;
      63             :     }
      64             : 
      65             :     // Register listener for every property
      66           2 :     for (final property in properties) {
      67           2 :       if (!_propertyListeners.containsKey(property)) {
      68           3 :         _propertyListeners[property] = ObserverList<Function>();
      69             :       }
      70           3 :       _addListener(_propertyListeners[property], listener);
      71             :     }
      72             :   }
      73             : 
      74             :   /// Removes [listener] for the given [properties]. [listener] must not be null.
      75             :   /// If [properties] is null or empty, [listener] will be removed as a global listener.
      76             :   /// Removing a listener will not affect any other properties [listeners] is registered for.
      77             :   /// Removing a non-existent listener is no-op.
      78             :   /// Removing a listener for a non-existent property will not fail.
      79           3 :   @override
      80             :   void removeListener(Function listener, [Iterable<T> properties]) {
      81           3 :     assert(_debugAssertNotDisposed());
      82           1 :     assert(listener != null);
      83             : 
      84             :     // Remove global listener only
      85           1 :     if (properties == null || properties.isEmpty) {
      86           6 :       _globalListeners.remove(listener);
      87             :       return;
      88             :     }
      89             : 
      90             :     // Remove listener for every property
      91           2 :     for (final property in properties) {
      92             :       // If no map entry exists for property, ignore
      93           2 :       if (!_propertyListeners.containsKey(property)) {
      94             :         continue;
      95             :       }
      96             : 
      97             :       // Remove listener
      98           2 :       final listeners = _propertyListeners[property];
      99           1 :       listeners.remove(listener);
     100             : 
     101             :       // Remove map entry if needed
     102           1 :       if (listeners.isEmpty) {
     103           2 :         _propertyListeners.remove(property);
     104             :       }
     105             :     }
     106             :   }
     107             : 
     108             :   /// Reimplemented from [ChangeNotifier].
     109             :   /// Discards any resources used by the object. After this is called, the
     110             :   /// object is not in a usable state and should be discarded (calls to
     111             :   /// [addListener] and [removeListener] will throw after the object is
     112             :   /// disposed).
     113             :   ///
     114             :   /// This method should only be called by the object's owner.
     115           1 :   @override
     116             :   @mustCallSuper
     117             :   void dispose() {
     118           1 :     assert(_debugAssertNotDisposed());
     119           1 :     _globalListeners = null;
     120           1 :     _propertyListeners = null;
     121           1 :     super.dispose();
     122             :   }
     123             : 
     124             :   /// Notifies the appropriate listeners that [property] was changed.
     125             :   /// Implementers should ideally provide a [property] parameter.
     126             :   /// It is only optional for backwards compatibility with [ChangeNotifier].
     127             :   /// Global listeners will be notified every time, even if [property] is null.
     128             :   /// Listeners for specific properties will only be notified
     129             :   /// if [property] is equal (operator==) to one of those properties.
     130             :   /// If [property] is not null, must be a single instance of [T] (typically a [String]).
     131           3 :   @override
     132             :   @protected
     133             :   @visibleForTesting
     134             :   void notifyListeners([T property]) {
     135           3 :     assert(_debugAssertNotDisposed());
     136           4 :     assert(!(property is Iterable), 'notifyListeners() should only be called for one property at a time');
     137             : 
     138             :     // Always notify global listeners
     139           6 :     _notifyListeners(_globalListeners, property);
     140             : 
     141             :     // If no property provided, exit
     142             :     if (property == null) {
     143             :       return;
     144             :     }
     145             : 
     146             :     // If listeners exist for this property, notify them.
     147           6 :     if (_propertyListeners.containsKey(property)) {
     148           3 :       _notifyListeners(_propertyListeners[property], property);
     149             :     }
     150             :   }
     151             : 
     152             :   /// Adds [listener] to [listeners] only if is not already present.
     153           3 :   void _addListener(ObserverList<Function> listeners, Function listener) {
     154           3 :     if (!listeners.contains(listener)) {
     155           3 :       listeners.add(listener);
     156             :     }
     157             :   }
     158             : 
     159             :   /// Creates a local copy of [listeners] in case a callback calls
     160             :   /// [addListener] or [removeListener] while iterating through the list.
     161             :   /// Invokes each listener. If the listener accepts a property parameter, it will be provided.
     162           3 :   void _notifyListeners(ObserverList<Function> listeners, T property) {
     163           3 :     final localListeners = List<Function>.from(listeners);
     164           6 :     for (final listener in localListeners) {
     165             :       // One last check to make sure the listener hasn't been removed
     166             :       // from the original list since the time we made our local copy.
     167           3 :       if (listeners.contains(listener)) {
     168           3 :         if (listener is PropertyCallback<T>) {
     169           3 :           listener(property);
     170             :         } else {
     171           1 :           listener();
     172             :         }
     173             :       }
     174             :     }
     175             :   }
     176             : 
     177             :   /// Reimplemented from [ChangeNotifier].
     178           3 :   bool _debugAssertNotDisposed() {
     179           3 :     assert(() {
     180           6 :       if (_globalListeners == null || _propertyListeners == null) {
     181           2 :         throw FlutterError('A $runtimeType was used after being disposed.\n'
     182           1 :             'Once you have called dispose() on a $runtimeType, it can no longer be used.');
     183             :       }
     184             :       return true;
     185           3 :     }());
     186             :     return true;
     187             :   }
     188             : }

Generated by: LCOV version 1.14