change_notifier.dart 10.1 KB
Newer Older
1 2 3 4
// 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.

5
import 'package:meta/meta.dart';
6 7 8

import 'assertions.dart';
import 'basic_types.dart';
9
import 'diagnostics.dart';
10
import 'observer_list.dart';
Ian Hickson's avatar
Ian Hickson committed
11 12

/// An object that maintains a list of listeners.
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
///
/// The listeners are typically used to notify clients that the object has been
/// updated.
///
/// There are two variants of this interface:
///
///  * [ValueListenable], an interface that augments the [Listenable] interface
///    with the concept of a _current value_.
///
///  * [Animation], an interface that augments the [ValueListenable] interface
///    to add the concept of direction (forward or reverse).
///
/// Many classes in the Flutter API use or implement these interfaces. The
/// following subclasses are especially relevant:
///
///  * [ChangeNotifier], which can be subclassed or mixed in to create objects
///    that implement the [Listenable] interface.
///
///  * [ValueNotifier], which implements the [ValueListenable] interface with
///    a mutable value that triggers the notifications when modified.
///
/// The terms "notify clients", "send notifications", "trigger notifications",
/// and "fire notifications" are used interchangeably.
///
/// See also:
///
///  * [AnimatedBuilder], a widget that uses a builder callback to rebuild
///    whenever a given [Listenable] triggers its notifications. This widget is
///    commonly used with [Animation] subclasses, wherein its name. It is a
///    subclass of [AnimatedWidget], which can be used to create widgets that
///    are driven from a [Listenable].
///  * [ValueListenableBuilder], a widget that uses a builder callback to
///    rebuild whenever a [ValueListenable] object triggers its notifications,
///    providing the builder with the value of the object.
///  * [InheritedNotifier], an abstract superclass for widgets that use a
///    [Listenable]'s notifications to trigger rebuilds in descendant widgets
///    that declare a dependency on them, using the [InheritedWidget] mechanism.
///  * [new Listenable.merge], which creates a [Listenable] that triggers
///    notifications whenever any of a list of other [Listenable]s trigger their
///    notifications.
Ian Hickson's avatar
Ian Hickson committed
53 54 55 56 57 58 59 60 61 62
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.
63
  ///
64
  /// The list may contain nulls; they are ignored.
Ian Hickson's avatar
Ian Hickson committed
65 66 67 68 69 70 71 72 73
  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);
}
74

75 76 77 78 79
/// An interface for subclasses of [Listenable] that expose a [value].
///
/// This interface is implemented by [ValueNotifier<T>] and [Animation<T>], and
/// allows other APIs to accept either of those implementations interchangeably.
abstract class ValueListenable<T> extends Listenable {
80 81 82 83
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const ValueListenable();

84 85 86 87 88
  /// The current value of the object. When the value changes, the callbacks
  /// registered with [addListener] will be invoked.
  T get value;
}

89 90
/// A class that can be extended or mixed in that provides a change notification
/// API using [VoidCallback] for notifications.
91
///
92
/// [ChangeNotifier] is optimized for small numbers (one or two) of listeners.
93 94
/// It is O(N) for adding and removing listeners and O(N²) for dispatching
/// notifications (where N is the number of listeners).
Adam Barth's avatar
Adam Barth committed
95 96 97 98
///
/// See also:
///
///  * [ValueNotifier], which is a [ChangeNotifier] that wraps a single value.
99
class ChangeNotifier implements Listenable {
100
  ObserverList<VoidCallback> _listeners = ObserverList<VoidCallback>();
101

102 103
  bool _debugAssertNotDisposed() {
    assert(() {
104
      if (_listeners == null) {
105
        throw FlutterError(
106 107 108 109 110
          'A $runtimeType was used after being disposed.\n'
          'Once you have called dispose() on a $runtimeType, it can no longer be used.'
        );
      }
      return true;
111
    }());
112 113 114
    return true;
  }

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
  /// 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;
  }

136
  /// Register a closure to be called when the object changes.
137 138
  ///
  /// This method must not be called after [dispose] has been called.
139
  @override
140
  void addListener(VoidCallback listener) {
141
    assert(_debugAssertNotDisposed());
142 143 144 145 146
    _listeners.add(listener);
  }

  /// Remove a previously registered closure from the list of closures that are
  /// notified when the object changes.
147 148 149 150
  ///
  /// If the given listener is not registered, the call is ignored.
  ///
  /// This method must not be called after [dispose] has been called.
151 152 153 154 155 156 157 158 159 160 161 162 163
  ///
  /// 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.
164
  @override
165
  void removeListener(VoidCallback listener) {
166
    assert(_debugAssertNotDisposed());
167
    _listeners.remove(listener);
168 169
  }

170 171 172 173
  /// 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).
174 175 176 177
  ///
  /// This method should only be called by the object's owner.
  @mustCallSuper
  void dispose() {
178
    assert(_debugAssertNotDisposed());
179
    _listeners = null;
180 181 182 183 184
  }

  /// Call all the registered listeners.
  ///
  /// Call this method whenever the object changes, to notify any clients the
185 186 187
  /// 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.
188 189 190
  ///
  /// Exceptions thrown by listeners will be caught and reported using
  /// [FlutterError.reportError].
191 192
  ///
  /// This method must not be called after [dispose] has been called.
193 194 195 196
  ///
  /// 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].
197
  @protected
198
  @visibleForTesting
199
  void notifyListeners() {
200
    assert(_debugAssertNotDisposed());
201
    if (_listeners != null) {
202
      final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
203
      for (VoidCallback listener in localListeners) {
204
        try {
205 206
          if (_listeners.contains(listener))
            listener();
207
        } catch (exception, stack) {
208
          FlutterError.reportError(FlutterErrorDetails(
209 210 211 212 213 214 215 216 217 218 219 220 221 222
            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
223

224 225
class _MergingListenable extends Listenable {
  _MergingListenable(this._children);
Ian Hickson's avatar
Ian Hickson committed
226 227 228 229

  final List<Listenable> _children;

  @override
230 231 232 233 234 235 236 237 238 239 240
  void addListener(VoidCallback listener) {
    for (final Listenable child  in _children) {
      child?.addListener(listener);
    }
  }

  @override
  void removeListener(VoidCallback listener) {
    for (final Listenable child in _children) {
      child?.removeListener(listener);
    }
Ian Hickson's avatar
Ian Hickson committed
241
  }
242 243 244

  @override
  String toString() {
245
    return 'Listenable.merge([${_children.join(", ")}])';
246
  }
Ian Hickson's avatar
Ian Hickson committed
247
}
Adam Barth's avatar
Adam Barth committed
248 249 250 251

/// A [ChangeNotifier] that holds a single value.
///
/// When [value] is replaced, this class notifies its listeners.
252
class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
Adam Barth's avatar
Adam Barth committed
253 254 255 256 257 258
  /// Creates a [ChangeNotifier] that wraps this value.
  ValueNotifier(this._value);

  /// The current value stored in this notifier.
  ///
  /// When the value is replaced, this class notifies its listeners.
259
  @override
Adam Barth's avatar
Adam Barth committed
260 261 262 263 264 265 266 267
  T get value => _value;
  T _value;
  set value(T newValue) {
    if (_value == newValue)
      return;
    _value = newValue;
    notifyListeners();
  }
268 269

  @override
270
  String toString() => '${describeIdentity(this)}($value)';
Adam Barth's avatar
Adam Barth committed
271
}