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-09-01 16:02:36 Functions: 0 0 -

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

Generated by: LCOV version 1.14