change_notifier.dart 18.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:ui' show VoidCallback;

7
import 'package:meta/meta.dart';
8 9

import 'assertions.dart';
10
import 'diagnostics.dart';
11
import 'memory_allocations.dart';
Ian Hickson's avatar
Ian Hickson committed
12

13 14
export 'dart:ui' show VoidCallback;

Ian Hickson's avatar
Ian Hickson committed
15
/// An object that maintains a list of listeners.
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
///
/// 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
44 45 46 47
///    commonly used with [Animation] subclasses, hence its name, but is by no
///    means limited to animations, as it can be used with any [Listenable]. It
///    is a subclass of [AnimatedWidget], which can be used to create widgets
///    that are driven from a [Listenable].
48 49 50 51 52 53
///  * [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.
54
///  * [Listenable.merge], which creates a [Listenable] that triggers
55 56
///    notifications whenever any of a list of other [Listenable]s trigger their
///    notifications.
Ian Hickson's avatar
Ian Hickson committed
57 58 59 60 61 62 63 64 65 66
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.
67
  ///
68
  /// The list may contain nulls; they are ignored.
69
  factory Listenable.merge(List<Listenable?> listenables) = _MergingListenable;
Ian Hickson's avatar
Ian Hickson committed
70 71 72 73 74 75 76 77

  /// 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);
}
78

79 80 81 82
/// 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.
83 84 85 86 87 88
///
/// See also:
///
///  * [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.
89
abstract class ValueListenable<T> extends Listenable {
90 91 92 93
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const ValueListenable();

94 95 96 97 98
  /// The current value of the object. When the value changes, the callbacks
  /// registered with [addListener] will be invoked.
  T get value;
}

99 100
const String _flutterFoundationLibrary = 'package:flutter/foundation.dart';

101 102
/// A class that can be extended or mixed in that provides a change notification
/// API using [VoidCallback] for notifications.
103
///
104
/// It is O(1) for adding listeners and O(N) for removing listeners and dispatching
105
/// notifications (where N is the number of listeners).
Adam Barth's avatar
Adam Barth committed
106
///
107
/// {@macro flutter.flutter.ListenableBuilder.ChangeNotifier.rebuild}
108
///
Adam Barth's avatar
Adam Barth committed
109 110 111
/// See also:
///
///  * [ValueNotifier], which is a [ChangeNotifier] that wraps a single value.
112
class ChangeNotifier implements Listenable {
113
  int _count = 0;
114
  // The _listeners is intentionally set to a fixed-length _GrowableList instead
115 116 117 118 119 120 121
  // of const [].
  //
  // The const [] creates an instance of _ImmutableList which would be
  // different from fixed-length _GrowableList used elsewhere in this class.
  // keeping runtime type the same during the lifetime of this class lets the
  // compiler to infer concrete type for this property, and thus improves
  // performance.
122 123
  static final List<VoidCallback?> _emptyListeners = List<VoidCallback?>.filled(0, null);
  List<VoidCallback?> _listeners = _emptyListeners;
124 125 126
  int _notificationCallStackDepth = 0;
  int _reentrantlyRemovedListeners = 0;
  bool _debugDisposed = false;
127

128 129 130 131 132 133 134
  /// If true, the event [ObjectCreated] for this instance was dispatched to
  /// [MemoryAllocations].
  ///
  /// As [ChangedNotifier] is used as mixin, it does not have constructor,
  /// so we use [addListener] to dispatch the event.
  bool _creationDispatched = false;

135 136 137 138
  /// Used by subclasses to assert that the [ChangeNotifier] has not yet been
  /// disposed.
  ///
  /// {@tool snippet}
139
  /// The [debugAssertNotDisposed] function should only be called inside of an
140 141 142 143 144
  /// assert, as in this example.
  ///
  /// ```dart
  /// class MyNotifier with ChangeNotifier {
  ///   void doUpdate() {
145
  ///     assert(ChangeNotifier.debugAssertNotDisposed(this));
146 147 148 149 150
  ///     // ...
  ///   }
  /// }
  /// ```
  /// {@end-tool}
151 152 153 154
  // This is static and not an instance method because too many people try to
  // implement ChangeNotifier instead of extending it (and so it is too breaking
  // to add a method, especially for debug).
  static bool debugAssertNotDisposed(ChangeNotifier notifier) {
155
    assert(() {
156
      if (notifier._debugDisposed) {
157
        throw FlutterError(
158 159 160
          'A ${notifier.runtimeType} was used after being disposed.\n'
          'Once you have called dispose() on a ${notifier.runtimeType}, it '
          'can no longer be used.',
161
        );
162 163
      }
      return true;
164
    }());
165 166 167
    return true;
  }

168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
  /// 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.
183 184
  ///
  /// This method returns false if [dispose] has been called.
185
  @protected
186
  bool get hasListeners => _count > 0;
187

188
  /// Register a closure to be called when the object changes.
189
  ///
190 191 192 193
  /// If the given closure is already registered, an additional instance is
  /// added, and must be removed the same number of times it is added before it
  /// will stop being called.
  ///
194
  /// This method must not be called after [dispose] has been called.
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
  ///
  /// {@template flutter.foundation.ChangeNotifier.addListener}
  /// If a listener is added twice, and is removed once during an iteration
  /// (e.g. 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, therefore it will conservatively still
  /// call 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.
  /// {@endtemplate}
  ///
  /// See also:
  ///
  ///  * [removeListener], which removes a previously registered closure from
  ///    the list of closures that are notified when the object changes.
214
  @override
215
  void addListener(VoidCallback listener) {
216
    assert(ChangeNotifier.debugAssertNotDisposed(this));
217
    if (kFlutterMemoryAllocationsEnabled && !_creationDispatched) {
218
      MemoryAllocations.instance.dispatchObjectCreated(
219
        library: _flutterFoundationLibrary,
220
        className: '$ChangeNotifier',
221
        object: this,
222
      );
223 224
      _creationDispatched = true;
    }
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
    if (_count == _listeners.length) {
      if (_count == 0) {
        _listeners = List<VoidCallback?>.filled(1, null);
      } else {
        final List<VoidCallback?> newListeners =
            List<VoidCallback?>.filled(_listeners.length * 2, null);
        for (int i = 0; i < _count; i++) {
          newListeners[i] = _listeners[i];
        }
        _listeners = newListeners;
      }
    }
    _listeners[_count++] = listener;
  }

  void _removeAt(int index) {
    // The list holding the listeners is not growable for performances reasons.
    // We still want to shrink this list if a lot of listeners have been added
    // and then removed outside a notifyListeners iteration.
    // We do this only when the real number of listeners is half the length
    // of our list.
    _count -= 1;
    if (_count * 2 <= _listeners.length) {
      final List<VoidCallback?> newListeners = List<VoidCallback?>.filled(_count, null);

      // Listeners before the index are at the same place.
251
      for (int i = 0; i < index; i++) {
252
        newListeners[i] = _listeners[i];
253
      }
254 255

      // Listeners after the index move towards the start of the list.
256
      for (int i = index; i < _count; i++) {
257
        newListeners[i] = _listeners[i + 1];
258
      }
259 260 261 262 263 264

      _listeners = newListeners;
    } else {
      // When there are more listeners than half the length of the list, we only
      // shift our listeners, so that we avoid to reallocate memory for the
      // whole list.
265
      for (int i = index; i < _count; i++) {
266
        _listeners[i] = _listeners[i + 1];
267
      }
268 269
      _listeners[_count] = null;
    }
270 271 272 273
  }

  /// Remove a previously registered closure from the list of closures that are
  /// notified when the object changes.
274 275 276
  ///
  /// If the given listener is not registered, the call is ignored.
  ///
277
  /// This method returns immediately if [dispose] has been called.
278
  ///
279
  /// {@macro flutter.foundation.ChangeNotifier.addListener}
280
  ///
281 282 283 284
  /// See also:
  ///
  ///  * [addListener], which registers a closure to be called when the object
  ///    changes.
285
  @override
286
  void removeListener(VoidCallback listener) {
287 288 289 290 291
    // This method is allowed to be called on disposed instances for usability
    // reasons. Due to how our frame scheduling logic between render objects and
    // overlays, it is common that the owner of this instance would be disposed a
    // frame earlier than the listeners. Allowing calls to this method after it
    // is disposed makes it easier for listeners to properly clean up.
292
    for (int i = 0; i < _count; i++) {
293 294
      final VoidCallback? listenerAtIndex = _listeners[i];
      if (listenerAtIndex == listener) {
295 296 297 298 299 300 301 302 303 304 305 306 307
        if (_notificationCallStackDepth > 0) {
          // We don't resize the list during notifyListeners iterations
          // but we set to null, the listeners we want to remove. We will
          // effectively resize the list at the end of all notifyListeners
          // iterations.
          _listeners[i] = null;
          _reentrantlyRemovedListeners++;
        } else {
          // When we are outside the notifyListeners iterations we can
          // effectively shrink the list.
          _removeAt(i);
        }
        break;
308 309
      }
    }
310 311
  }

312 313
  /// 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
314
  /// [addListener] will throw after the object is disposed).
315 316
  ///
  /// This method should only be called by the object's owner.
317 318 319 320
  ///
  /// This method does not notify listeners, and clears the listener list once
  /// it is called. Consumers of this class must decide on whether to notify
  /// listeners or not immediately before disposal.
321 322
  @mustCallSuper
  void dispose() {
323
    assert(ChangeNotifier.debugAssertNotDisposed(this));
324 325 326 327 328 329
    assert(
      _notificationCallStackDepth == 0,
      'The "dispose()" method on $this was called during the call to '
      '"notifyListeners()". This is likely to cause errors since it modifies '
      'the list of listeners while the list is being used.',
    );
330 331 332 333
    assert(() {
      _debugDisposed = true;
      return true;
    }());
334
    if (kFlutterMemoryAllocationsEnabled && _creationDispatched) {
335
      MemoryAllocations.instance.dispatchObjectDisposed(object: this);
336
    }
337 338
    _listeners = _emptyListeners;
    _count = 0;
339 340 341 342 343
  }

  /// Call all the registered listeners.
  ///
  /// Call this method whenever the object changes, to notify any clients the
344 345 346
  /// object may have changed. 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.
347 348 349
  ///
  /// Exceptions thrown by listeners will be caught and reported using
  /// [FlutterError.reportError].
350 351
  ///
  /// This method must not be called after [dispose] has been called.
352
  ///
353
  /// Surprising behavior can result when reentrantly removing a listener (e.g.
354 355
  /// in response to a notification) that has been registered multiple times.
  /// See the discussion at [removeListener].
356
  @protected
357
  @visibleForTesting
358
  @pragma('vm:notify-debugger-on-exception')
359
  void notifyListeners() {
360
    assert(ChangeNotifier.debugAssertNotDisposed(this));
361
    if (_count == 0) {
362
      return;
363
    }
364

365 366 367 368 369 370 371 372 373 374
    // To make sure that listeners removed during this iteration are not called,
    // we set them to null, but we don't shrink the list right away.
    // By doing this, we can continue to iterate on our list until it reaches
    // the last listener added before the call to this method.

    // To allow potential listeners to recursively call notifyListener, we track
    // the number of times this method is called in _notificationCallStackDepth.
    // Once every recursive iteration is finished (i.e. when _notificationCallStackDepth == 0),
    // we can safely shrink our list so that it will only contain not null
    // listeners.
375

376 377 378 379
    _notificationCallStackDepth++;

    final int end = _count;
    for (int i = 0; i < end; i++) {
380
      try {
381
        _listeners[i]?.call();
382 383 384 385 386 387
      } catch (exception, stack) {
        FlutterError.reportError(FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'foundation library',
          context: ErrorDescription('while dispatching notifications for $runtimeType'),
388 389
          informationCollector: () => <DiagnosticsNode>[
            DiagnosticsProperty<ChangeNotifier>(
390 391 392
              'The $runtimeType sending notification was',
              this,
              style: DiagnosticsTreeStyle.errorProperty,
393 394
            ),
          ],
395
        ));
396 397
      }
    }
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423

    _notificationCallStackDepth--;

    if (_notificationCallStackDepth == 0 && _reentrantlyRemovedListeners > 0) {
      // We really remove the listeners when all notifications are done.
      final int newLength = _count - _reentrantlyRemovedListeners;
      if (newLength * 2 <= _listeners.length) {
        // As in _removeAt, we only shrink the list when the real number of
        // listeners is half the length of our list.
        final List<VoidCallback?> newListeners = List<VoidCallback?>.filled(newLength, null);

        int newIndex = 0;
        for (int i = 0; i < _count; i++) {
          final VoidCallback? listener = _listeners[i];
          if (listener != null) {
            newListeners[newIndex++] = listener;
          }
        }

        _listeners = newListeners;
      } else {
        // Otherwise we put all the null references at the end.
        for (int i = 0; i < newLength; i += 1) {
          if (_listeners[i] == null) {
            // We swap this item with the next not null item.
            int swapIndex = i + 1;
424
            while(_listeners[swapIndex] == null) {
425 426 427 428 429 430 431 432 433 434 435
              swapIndex += 1;
            }
            _listeners[i] = _listeners[swapIndex];
            _listeners[swapIndex] = null;
          }
        }
      }

      _reentrantlyRemovedListeners = 0;
      _count = newLength;
    }
436 437
  }
}
Ian Hickson's avatar
Ian Hickson committed
438

439 440
class _MergingListenable extends Listenable {
  _MergingListenable(this._children);
Ian Hickson's avatar
Ian Hickson committed
441

442
  final List<Listenable?> _children;
Ian Hickson's avatar
Ian Hickson committed
443 444

  @override
445
  void addListener(VoidCallback listener) {
446
    for (final Listenable? child in _children) {
447 448 449 450 451 452
      child?.addListener(listener);
    }
  }

  @override
  void removeListener(VoidCallback listener) {
453
    for (final Listenable? child in _children) {
454 455
      child?.removeListener(listener);
    }
Ian Hickson's avatar
Ian Hickson committed
456
  }
457 458 459

  @override
  String toString() {
460
    return 'Listenable.merge([${_children.join(", ")}])';
461
  }
Ian Hickson's avatar
Ian Hickson committed
462
}
Adam Barth's avatar
Adam Barth committed
463 464 465

/// A [ChangeNotifier] that holds a single value.
///
466 467 468
/// When [value] is replaced with something that is not equal to the old
/// value as evaluated by the equality operator ==, this class notifies its
/// listeners.
469
class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
Adam Barth's avatar
Adam Barth committed
470
  /// Creates a [ChangeNotifier] that wraps this value.
471 472
  ValueNotifier(this._value) {
    if (kFlutterMemoryAllocationsEnabled) {
473
      MemoryAllocations.instance.dispatchObjectCreated(
474
        library: _flutterFoundationLibrary,
475
        className: '$ValueNotifier',
476
        object: this,
477
      );
478 479 480
    }
    _creationDispatched = true;
  }
Adam Barth's avatar
Adam Barth committed
481 482 483

  /// The current value stored in this notifier.
  ///
484 485 486
  /// When the value is replaced with something that is not equal to the old
  /// value as evaluated by the equality operator ==, this class notifies its
  /// listeners.
487
  @override
Adam Barth's avatar
Adam Barth committed
488 489 490
  T get value => _value;
  T _value;
  set value(T newValue) {
491
    if (_value == newValue) {
Adam Barth's avatar
Adam Barth committed
492
      return;
493
    }
Adam Barth's avatar
Adam Barth committed
494 495 496
    _value = newValue;
    notifyListeners();
  }
497 498

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