1
2
3
4
5
6
7
8
9
10
11
12
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// 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';
import 'diagnostics.dart';
import 'observer_list.dart';
/// 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.
///
/// The list may contain nulls; they are ignored.
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);
}
/// 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 {
/// The current value of the object. When the value changes, the callbacks
/// registered with [addListener] will be invoked.
T get value;
}
/// A class that can be extended or mixed in that provides a change notification
/// API using [VoidCallback] for notifications.
///
/// [ChangeNotifier] is optimized 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).
///
/// See also:
///
/// * [ValueNotifier], which is a [ChangeNotifier] that wraps a single value.
class ChangeNotifier extends Listenable {
ObserverList<VoidCallback> _listeners = new ObserverList<VoidCallback>();
bool _debugAssertNotDisposed() {
assert(() {
if (_listeners == null) {
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;
}
/// 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.
@override
void addListener(VoidCallback listener) {
assert(_debugAssertNotDisposed());
_listeners.add(listener);
}
/// Remove a previously registered closure from the list of closures that are
/// notified when the object changes.
///
/// If the given listener is not registered, the call is ignored.
///
/// This method must not be called after [dispose] has been called.
///
/// 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.
@override
void removeListener(VoidCallback listener) {
assert(_debugAssertNotDisposed());
_listeners.remove(listener);
}
/// 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).
///
/// This method should only be called by the object's owner.
@mustCallSuper
void dispose() {
assert(_debugAssertNotDisposed());
_listeners = null;
}
/// Call all the registered listeners.
///
/// Call this method whenever the object changes, to notify any clients the
/// 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.
///
/// Exceptions thrown by listeners will be caught and reported using
/// [FlutterError.reportError].
///
/// This method must not be called after [dispose] has been called.
///
/// 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].
@protected
void notifyListeners() {
assert(_debugAssertNotDisposed());
if (_listeners != null) {
final List<VoidCallback> localListeners = new List<VoidCallback>.from(_listeners);
for (VoidCallback listener in localListeners) {
try {
if (_listeners.contains(listener))
listener();
} 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');
}
));
}
}
}
}
}
class _MergingListenable extends ChangeNotifier {
_MergingListenable(this._children) {
for (Listenable child in _children)
child?.addListener(notifyListeners);
}
final List<Listenable> _children;
@override
void dispose() {
for (Listenable child in _children)
child?.removeListener(notifyListeners);
super.dispose();
}
@override
String toString() {
return 'Listenable.merge([${_children.join(", ")}])';
}
}
/// A [ChangeNotifier] that holds a single value.
///
/// When [value] is replaced, this class notifies its listeners.
class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
/// 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.
@override
T get value => _value;
T _value;
set value(T newValue) {
if (_value == newValue)
return;
_value = newValue;
notifyListeners();
}
@override
String toString() => '${describeIdentity(this)}($value)';
}