change_notifier.dart 5.61 KB
Newer Older
1 2 3 4 5 6 7 8
// Copyright 2015 The Chromium 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 'package:meta/meta.dart';

import 'assertions.dart';
import 'basic_types.dart';
9
import 'observer_list.dart';
Ian Hickson's avatar
Ian Hickson committed
10 11 12 13 14 15 16 17 18 19 20 21

/// An object that maintains a list of listeners.
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.
22 23
  ///
  /// The list may contain `null`s; they are ignored.
Ian Hickson's avatar
Ian Hickson committed
24 25 26 27 28 29 30 31 32
  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);
}
33

34 35
/// A class that can be extended or mixed in that provides a change notification
/// API using [VoidCallback] for notifications.
36 37 38 39
///
/// [ChangeNotifier] is optimised for small numbers (one or two) of listeners.
/// It is O(N) for adding and removing listeners and O(N²) for dispatching
/// notifications (where N is the number of listeners).
40
class ChangeNotifier extends Listenable {
41
  ObserverList<VoidCallback> _listeners = new ObserverList<VoidCallback>();
42

43 44
  bool _debugAssertNotDisposed() {
    assert(() {
45
      if (_listeners == null) {
46 47 48 49 50 51 52 53 54 55
        throw new 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;
  }

56
  /// Register a closure to be called when the object changes.
57 58
  ///
  /// This method must not be called after [dispose] has been called.
59
  @override
60
  void addListener(VoidCallback listener) {
61
    assert(_debugAssertNotDisposed);
62 63 64 65 66
    _listeners.add(listener);
  }

  /// Remove a previously registered closure from the list of closures that are
  /// notified when the object changes.
67 68 69 70
  ///
  /// If the given listener is not registered, the call is ignored.
  ///
  /// This method must not be called after [dispose] has been called.
71 72 73 74 75 76 77 78 79 80 81 82 83
  ///
  /// 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.
84
  @override
85
  void removeListener(VoidCallback listener) {
86
    assert(_debugAssertNotDisposed);
87
    _listeners.remove(listener);
88 89
  }

90 91 92 93
  /// 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).
94 95 96 97
  ///
  /// This method should only be called by the object's owner.
  @mustCallSuper
  void dispose() {
98
    assert(_debugAssertNotDisposed);
99
    _listeners = null;
100 101 102 103 104
  }

  /// Call all the registered listeners.
  ///
  /// Call this method whenever the object changes, to notify any clients the
105 106 107
  /// 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.
108 109 110
  ///
  /// Exceptions thrown by listeners will be caught and reported using
  /// [FlutterError.reportError].
111 112
  ///
  /// This method must not be called after [dispose] has been called.
113 114 115 116
  ///
  /// 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].
117 118
  @protected
  void notifyListeners() {
119
    assert(_debugAssertNotDisposed);
120
    if (_listeners != null) {
121
      final List<VoidCallback> localListeners = new List<VoidCallback>.from(_listeners);
122
      for (VoidCallback listener in localListeners) {
123
        try {
124 125
          if (_listeners.contains(listener))
            listener();
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
        } catch (exception, stack) {
          FlutterError.reportError(new FlutterErrorDetails(
            exception: exception,
            stack: stack,
            library: 'foundation library',
            context: 'while dispatching notifications for $runtimeType',
            informationCollector: (StringBuffer information) {
              information.writeln('The $runtimeType sending notification was:');
              information.write('  $this');
            }
          ));
        }
      }
    }
  }
}
Ian Hickson's avatar
Ian Hickson committed
142 143 144 145

class _MergingListenable extends ChangeNotifier {
  _MergingListenable(this._children) {
    for (Listenable child in _children)
146
      child?.addListener(notifyListeners);
Ian Hickson's avatar
Ian Hickson committed
147 148 149 150 151 152 153
  }

  final List<Listenable> _children;

  @override
  void dispose() {
    for (Listenable child in _children)
154
      child?.removeListener(notifyListeners);
Ian Hickson's avatar
Ian Hickson committed
155 156 157
    super.dispose();
  }
}