LCOV - code coverage report
Current view: top level - src - property_change_notifier.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 49 49 100.0 %
Date: 2021-09-17 16:22:12 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             :   ObserverList<Function>? _globalListeners = ObserverList<Function>();
      21             :   Map<T, ObserverList<Function>>? _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           7 :     assert(listener is VoidCallback || listener is PropertyCallback<T>, 'Listener must be a Function() or Function(T?)');
      57             : 
      58             :     // Register global listener only
      59           1 :     if (properties == null || properties.isEmpty) {
      60           6 :       _addListener(_globalListeners!, listener);
      61             :       return;
      62             :     }
      63             : 
      64             :     // Register listener for every property
      65           2 :     for (final property in properties) {
      66           2 :       if (!_propertyListeners!.containsKey(property)) {
      67           3 :         _propertyListeners![property] = ObserverList<Function>();
      68             :       }
      69           3 :       _addListener(_propertyListeners![property]!, listener);
      70             :     }
      71             :   }
      72             : 
      73             :   /// Removes [listener] for the given [properties]. [listener] must not be null.
      74             :   /// If [properties] is null or empty, [listener] will be removed as a global listener.
      75             :   /// Removing a listener will not affect any other properties [listeners] is registered for.
      76             :   /// Removing a non-existent listener is no-op.
      77             :   /// Removing a listener for a non-existent property will not fail.
      78           3 :   @override
      79             :   void removeListener(Function listener, [Iterable<T>? properties]) {
      80           3 :     assert(_debugAssertNotDisposed());
      81             : 
      82             :     // Remove global listener only
      83           1 :     if (properties == null || properties.isEmpty) {
      84           6 :       _globalListeners!.remove(listener);
      85             :       return;
      86             :     }
      87             : 
      88             :     // Remove listener for every property
      89           2 :     for (final property in properties) {
      90             :       // If no map entry exists for property, ignore
      91           2 :       if (!_propertyListeners!.containsKey(property)) {
      92             :         continue;
      93             :       }
      94             : 
      95             :       // Remove listener
      96           2 :       final listeners = _propertyListeners![property]!;
      97           1 :       listeners.remove(listener);
      98             : 
      99             :       // Remove map entry if needed
     100           1 :       if (listeners.isEmpty) {
     101           2 :         _propertyListeners!.remove(property);
     102             :       }
     103             :     }
     104             :   }
     105             : 
     106             :   /// Reimplemented from [ChangeNotifier].
     107             :   /// Discards any resources used by the object. After this is called, the
     108             :   /// object is not in a usable state and should be discarded (calls to
     109             :   /// [addListener] and [removeListener] will throw after the object is
     110             :   /// disposed).
     111             :   ///
     112             :   /// This method should only be called by the object's owner.
     113           1 :   @override
     114             :   @mustCallSuper
     115             :   void dispose() {
     116           1 :     assert(_debugAssertNotDisposed());
     117           1 :     _globalListeners = null;
     118           1 :     _propertyListeners = null;
     119           1 :     super.dispose();
     120             :   }
     121             : 
     122             :   /// Notifies the appropriate listeners that [property] was changed.
     123             :   /// Implementers should ideally provide a [property] parameter.
     124             :   /// It is only optional for backwards compatibility with [ChangeNotifier].
     125             :   /// Global listeners will be notified every time, even if [property] is null.
     126             :   /// Listeners for specific properties will only be notified
     127             :   /// if [property] is equal (operator==) to one of those properties.
     128             :   /// If [property] is not null, must be a single instance of [T] (typically a [String]).
     129           3 :   @override
     130             :   @protected
     131             :   @visibleForTesting
     132             :   void notifyListeners([T? property]) {
     133           3 :     assert(_debugAssertNotDisposed());
     134           4 :     assert(property is! Iterable, 'notifyListeners() should only be called for one property at a time');
     135             : 
     136             :     // Always notify global listeners
     137           6 :     _notifyListeners(_globalListeners!, property);
     138             : 
     139             :     // If no property provided, exit
     140             :     if (property == null) {
     141             :       return;
     142             :     }
     143             : 
     144             :     // If listeners exist for this property, notify them.
     145           6 :     if (_propertyListeners!.containsKey(property)) {
     146           3 :       _notifyListeners(_propertyListeners![property]!, property);
     147             :     }
     148             :   }
     149             : 
     150             :   /// Adds [listener] to [listeners] only if is not already present.
     151           3 :   void _addListener(ObserverList<Function> listeners, Function listener) {
     152           3 :     if (!listeners.contains(listener)) {
     153           3 :       listeners.add(listener);
     154             :     }
     155             :   }
     156             : 
     157             :   /// Creates a local copy of [listeners] in case a callback calls
     158             :   /// [addListener] or [removeListener] while iterating through the list.
     159             :   /// Invokes each listener. If the listener accepts a property parameter, it will be provided.
     160           3 :   void _notifyListeners(ObserverList<Function> listeners, T? property) {
     161           3 :     final localListeners = List<Function>.from(listeners);
     162           6 :     for (final listener in localListeners) {
     163             :       // One last check to make sure the listener hasn't been removed
     164             :       // from the original list since the time we made our local copy.
     165           3 :       if (listeners.contains(listener)) {
     166           3 :         if (listener is PropertyCallback<T>) {
     167           3 :           listener(property);
     168             :         } else {
     169           1 :           listener();
     170             :         }
     171             :       }
     172             :     }
     173             :   }
     174             : 
     175             :   /// Reimplemented from [ChangeNotifier].
     176           3 :   bool _debugAssertNotDisposed() {
     177           3 :     assert(() {
     178           6 :       if (_globalListeners == null || _propertyListeners == null) {
     179           3 :         throw FlutterError('A $runtimeType was used after being disposed.\n'
     180           1 :             'Once you have called dispose() on a $runtimeType, it can no longer be used.');
     181             :       }
     182             :       return true;
     183           3 :     }());
     184             :     return true;
     185             :   }
     186             : }
     187             : 
     188             : /// A convenience typedef to use in the common use case where property names are of type [String].
     189             : typedef StringPropertyChangeNotifier = PropertyChangeNotifier<String>;

Generated by: LCOV version 1.14