From b9a7f1b915349210c7a80b1ac158ed43fd88f612 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Wed, 28 Feb 2018 16:37:36 -0800 Subject: [PATCH] Add a hasListeners to ChangeNotifier (#14946) I found that some ValueListeners want to know when they should start doing work (e.g. if the value comes from polling a network resource). --- .../lib/src/foundation/change_notifier.dart | 21 ++++++++++++++ .../lib/src/foundation/observer_list.dart | 12 ++++++-- .../test/foundation/change_notifier_test.dart | 28 +++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/foundation/change_notifier.dart b/packages/flutter/lib/src/foundation/change_notifier.dart index 2ea4d5d75d..88f9abcab9 100644 --- a/packages/flutter/lib/src/foundation/change_notifier.dart +++ b/packages/flutter/lib/src/foundation/change_notifier.dart @@ -68,6 +68,27 @@ class ChangeNotifier extends Listenable { return true; } + /// 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; + } + /// Register a closure to be called when the object changes. /// /// This method must not be called after [dispose] has been called. diff --git a/packages/flutter/lib/src/foundation/observer_list.dart b/packages/flutter/lib/src/foundation/observer_list.dart index be14d12034..125f3f92b6 100644 --- a/packages/flutter/lib/src/foundation/observer_list.dart +++ b/packages/flutter/lib/src/foundation/observer_list.dart @@ -8,14 +8,14 @@ import 'dart:collection'; /// /// Consider using an [ObserverList] instead of a [List] when the number of /// [contains] calls dominates the number of [add] and [remove] calls. +// TODO(ianh): Use DelegatingIterable, possibly moving it from the collection +// package to foundation, or to dart:collection. class ObserverList extends Iterable { final List _list = []; bool _isDirty = false; HashSet _set; /// Adds an item to the end of this list. - /// - /// The given item must not already be in the list. void add(T item) { _isDirty = true; _list.add(item); @@ -23,6 +23,8 @@ class ObserverList extends Iterable { /// Removes an item from the list. /// + /// This is O(N) in the number of items in the list. + /// /// Returns whether the item was present in the list. bool remove(T item) { _isDirty = true; @@ -49,4 +51,10 @@ class ObserverList extends Iterable { @override Iterator get iterator => _list.iterator; + + @override + bool get isEmpty => _list.isEmpty; + + @override + bool get isNotEmpty => _list.isNotEmpty; } diff --git a/packages/flutter/test/foundation/change_notifier_test.dart b/packages/flutter/test/foundation/change_notifier_test.dart index af5186dbbb..87fda33d16 100644 --- a/packages/flutter/test/foundation/change_notifier_test.dart +++ b/packages/flutter/test/foundation/change_notifier_test.dart @@ -235,4 +235,32 @@ void main() { "Listenable.merge([null, Instance of 'TestNotifier'])", ); }); + + test('hasListeners', () { + final HasListenersTester notifier = new HasListenersTester(true); + expect(notifier.testHasListeners, isFalse); + void test1() { } + void test2() { } + notifier.addListener(test1); + expect(notifier.testHasListeners, isTrue); + notifier.addListener(test1); + expect(notifier.testHasListeners, isTrue); + notifier.removeListener(test1); + expect(notifier.testHasListeners, isTrue); + notifier.removeListener(test1); + expect(notifier.testHasListeners, isFalse); + notifier.addListener(test1); + expect(notifier.testHasListeners, isTrue); + notifier.addListener(test2); + expect(notifier.testHasListeners, isTrue); + notifier.removeListener(test1); + expect(notifier.testHasListeners, isTrue); + notifier.removeListener(test2); + expect(notifier.testHasListeners, isFalse); + }); +} + +class HasListenersTester extends ValueNotifier { + HasListenersTester(T value) : super(value); + bool get testHasListeners => hasListeners; }