OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * A library to observe changes on Dart objects. | 6 * A library to observe changes on Dart objects. |
7 * | 7 * |
8 * Similar to the principle of watchers in AngularJS, this library provides the | 8 * Similar to the principle of watchers in AngularJS, this library provides the |
9 * mechanisms to observe and react to changes that happen in an application's | 9 * mechanisms to observe and react to changes that happen in an application's |
10 * data model. | 10 * data model. |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
43 * A common design pattern for MVC applications is to call [dispatch] at the end | 43 * A common design pattern for MVC applications is to call [dispatch] at the end |
44 * of each event loop (e.g. after each UI event is fired). Our view library does | 44 * of each event loop (e.g. after each UI event is fired). Our view library does |
45 * this automatically. | 45 * this automatically. |
46 */ | 46 */ |
47 library watcher; | 47 library watcher; |
48 | 48 |
49 import 'dart:async'; | 49 import 'dart:async'; |
50 import 'dart:collection' hide LinkedList, LinkedListEntry; | 50 import 'dart:collection' hide LinkedList, LinkedListEntry; |
51 import 'observe.dart'; | 51 import 'observe.dart'; |
52 import 'src/linked_list.dart'; | 52 import 'src/linked_list.dart'; |
| 53 import 'package:logging/logging.dart'; |
53 | 54 |
54 /** | 55 /** |
55 * True to use the [observe] library instead of watchers. | 56 * True to use the [observe] library instead of watchers. |
56 * | 57 * |
57 * Observers require the [observable] annotation on objects and for collection | 58 * Observers require the [observable] annotation on objects and for collection |
58 * types to be observable, such as [ObservableList]. But in return they offer | 59 * types to be observable, such as [ObservableList]. But in return they offer |
59 * better performance and more precise change tracking. [dispatch] is not | 60 * better performance and more precise change tracking. [dispatch] is not |
60 * required with observers, and changes to observable objects are always | 61 * required with observers, and changes to observable objects are always |
61 * detected. | 62 * detected. |
62 * | 63 * |
63 * Currently this flag is experimental, but it may be the default in the future. | 64 * Currently this flag is experimental, but it may be the default in the future. |
64 */ | 65 */ |
65 bool useObservers = false; | 66 bool useObservers = false; |
66 | 67 |
67 /** | 68 /** |
| 69 * True to enable more verbose debugging messages. This is useful for tracing |
| 70 * down errors that produce loops in the watcher evaluation order, for example |
| 71 * when you see an error message of the form "_Possible loop in watchers |
| 72 * propagation, stopped dispatch_". |
| 73 */ |
| 74 bool verboseDebugMessages = false; |
| 75 |
| 76 |
| 77 /** |
| 78 * Function used to record the current stack trace when [verboseDebugMessages] |
| 79 * is true. This can be replaced if you prefer to format strack traces |
| 80 * differently or filter frames of the stack traces. For |
| 81 * example using <http://pub.dartlang.org/packages/stack_trace>, you can do: |
| 82 * |
| 83 * import 'package:watcher/watcher.dart'; |
| 84 * import 'package:stack_trace/stack_trace.dart'; |
| 85 * main() { |
| 86 * verboseDebugMessages = true; |
| 87 * readCurrentStackTrace = () { |
| 88 * try { throw "" ; } catch (e, trace) { |
| 89 * return new Trace.from(trace).terse.toString(); |
| 90 * } |
| 91 * }; |
| 92 * ... |
| 93 * } |
| 94 */ |
| 95 Function readCurrentStackTrace = () { |
| 96 try { |
| 97 throw ""; |
| 98 } catch (e, trace) { |
| 99 return trace.toString(); |
| 100 } |
| 101 }; |
| 102 |
| 103 /** |
| 104 * Log for messages produced at runtime by this library. Logging can be |
| 105 * configured by accessing Logger.root from the logging library. |
| 106 */ |
| 107 final Logger _logger = new Logger('watcher'); |
| 108 |
| 109 /** |
68 * Watch for changes in [target]. The [callback] function will be called when | 110 * Watch for changes in [target]. The [callback] function will be called when |
69 * [dispatch] is called and the value represented by [target] had changed. The | 111 * [dispatch] is called and the value represented by [target] had changed. The |
70 * returned function can be used to unregister this watcher. | 112 * returned function can be used to unregister this watcher. |
71 * | 113 * |
72 * There are several values you can use for [target]: | 114 * There are several values you can use for [target]: |
73 * | 115 * |
74 * * A [Getter] function. | 116 * * A [Getter] function. |
75 * Use this to watch expressions as they change. For instance, to watch | 117 * Use this to watch expressions as they change. For instance, to watch |
76 * whether `a.b.c` changes, wrap it in a getter and call [watch] as follows: | 118 * whether `a.b.c` changes, wrap it in a getter and call [watch] as follows: |
77 * watch(() => a.b.c, ...) | 119 * watch(() => a.b.c, ...) |
78 * These targets are tracked to check for equality. If calling `target()` | 120 * These targets are tracked to check for equality. If calling `target()` |
79 * returns the same result, then the [callback] will not be invoked. In the | 121 * returns the same result, then the [callback] will not be invoked. In the |
80 * special case whe the getter returns a [List], we will treat the value in a | 122 * special case whe the getter returns a [List], we will treat the value in a |
81 * special way, similar to passing [List] directly as [target]. | 123 * special way, similar to passing [List] directly as [target]. |
82 * **Important**: this library assumes that [Getter] is a read-only function | 124 * **Important**: this library assumes that [Getter] is a read-only function |
83 * and that it will consistently return the same value if called multiple | 125 * and that it will consistently return the same value if called multiple |
84 * times in a row. | 126 * times in a row. |
85 * | 127 * |
86 * * A [List]. | 128 * * A [List]. |
87 * Use this to watch whether a list object changes. For instance, to detect | 129 * Use this to watch whether a list object changes. For instance, to detect |
88 * if an element is added or changed in a list can call [watch] as follows: | 130 * if an element is added or changed in a list can call [watch] as follows: |
89 * watch(list, ...) | 131 * watch(list, ...) |
90 * | 132 * |
91 * * A [Handle]. | 133 * * A [Handle]. |
92 * This is syntactic sugar for using the getter portion of a [Handle]. | 134 * This is syntactic sugar for using the getter portion of a [Handle]. |
93 * watch(handle, ...) // equivalent to `watch(handle._getter, ...)` | 135 * watch(handle, ...) // equivalent to `watch(handle._getter, ...)` |
94 */ | 136 */ |
95 ChangeUnobserver watch(target, ChangeObserver callback, [String debugName]) { | 137 ChangeUnobserver watch(target, ChangeObserver callback, |
| 138 [String debugName, String location]) { |
96 if (useObservers) return observe(target, callback); | 139 if (useObservers) return observe(target, callback); |
97 | 140 |
98 if (callback == null) return () {}; // no use in passing null as a callback. | 141 if (callback == null) return () {}; // no use in passing null as a callback. |
99 if (_watchers == null) _watchers = new LinkedList<_Watcher>(); | 142 if (_watchers == null) _watchers = new LinkedList<_Watcher>(); |
| 143 debugName = debugName == null ? '<unnamed>' : debugName; |
100 Function exp; | 144 Function exp; |
101 _WatcherType watcherType = _WatcherType.OTHER; | 145 _WatcherType watcherType = _WatcherType.OTHER; |
102 if (target is Handle) { | 146 if (target is Handle) { |
103 exp = (target as Handle)._getter; | 147 exp = (target as Handle)._getter; |
104 } else if (target is Function) { | 148 } else if (target is Function) { |
105 exp = target; | 149 exp = target; |
106 try { | 150 try { |
107 var val = target(); | 151 var val = target(); |
108 if (val is List) { | 152 if (val is List) { |
109 watcherType = _WatcherType.LIST; | 153 watcherType = _WatcherType.LIST; |
110 } else if (val is Iterable) { | 154 } else if (val is Iterable) { |
111 watcherType = _WatcherType.LIST; | 155 watcherType = _WatcherType.LIST; |
112 exp = () => target().toList(); | 156 exp = () => target().toList(); |
113 } else if ((val is LinkedHashMap) || (val is SplayTreeMap)) { | 157 } else if ((val is LinkedHashMap) || (val is SplayTreeMap)) { |
114 watcherType = _WatcherType.ORDERED_MAP; | 158 watcherType = _WatcherType.ORDERED_MAP; |
115 } else if (val is Map) { | 159 } else if (val is Map) { |
116 watcherType = _WatcherType.HASH_MAP; | 160 watcherType = _WatcherType.HASH_MAP; |
117 } | 161 } |
118 } catch (e, trace) { // in case target() throws some error | 162 } catch (e, trace) { // in case target() throws some error |
119 // TODO(sigmund): use logging instead of print when logger is in the SDK | 163 _logger.warning('evaluating $debugName watcher threw error ($e, $trace)'); |
120 // and available via pub (see dartbug.com/4363) | |
121 print('error: evaluating ${debugName != null ? debugName : "<unnamed>"} ' | |
122 'watcher threw error ($e, $trace)'); | |
123 } | 164 } |
124 } else if (target is List) { | 165 } else if (target is List) { |
125 exp = () => target; | 166 exp = () => target; |
126 watcherType = _WatcherType.LIST; | 167 watcherType = _WatcherType.LIST; |
127 } else if (target is Iterable) { | 168 } else if (target is Iterable) { |
128 exp = () => target.toList(); | 169 exp = () => target.toList(); |
129 watcherType = _WatcherType.LIST; | 170 watcherType = _WatcherType.LIST; |
130 } else if ((target is LinkedHashMap) || (target is SplayTreeMap)) { | 171 } else if ((target is LinkedHashMap) || (target is SplayTreeMap)) { |
131 exp = () => target; | 172 exp = () => target; |
132 watcherType = _WatcherType.ORDERED_MAP; | 173 watcherType = _WatcherType.ORDERED_MAP; |
133 } else if (target is Map) { | 174 } else if (target is Map) { |
134 exp = () => target; | 175 exp = () => target; |
135 watcherType = _WatcherType.HASH_MAP; | 176 watcherType = _WatcherType.HASH_MAP; |
136 } | 177 } |
137 | 178 |
138 var watcher = _createWatcher(watcherType, exp, callback, debugName); | 179 if (verboseDebugMessages && location == null |
| 180 && readCurrentStackTrace != null) { |
| 181 location = readCurrentStackTrace(); |
| 182 } |
| 183 |
| 184 var watcher = _createWatcher(watcherType, exp, callback, debugName, location); |
139 var node = _watchers.add(watcher); | 185 var node = _watchers.add(watcher); |
140 return node.remove; | 186 return node.remove; |
141 } | 187 } |
142 | 188 |
143 /** | 189 /** |
144 * Creates a watcher for [exp] of [type] with [callback] function and | 190 * Creates a watcher for [exp] of [type] with [callback] function and |
145 * [debugName]. | 191 * [debugName]. |
146 */ | 192 */ |
147 _Watcher _createWatcher(_WatcherType type, Function exp, | 193 _Watcher _createWatcher(_WatcherType type, Function exp, |
148 ChangeObserver callback, String debugName) { | 194 ChangeObserver callback, String debugName, String location) { |
149 switch(type) { | 195 switch(type) { |
150 case _WatcherType.LIST: | 196 case _WatcherType.LIST: |
151 return new _ListWatcher(exp, callback, debugName); | 197 return new _ListWatcher(exp, callback, debugName, location); |
152 case _WatcherType.ORDERED_MAP: | 198 case _WatcherType.ORDERED_MAP: |
153 return new _OrderDependantMapWatcher(exp, callback, debugName); | 199 return new _OrderDependantMapWatcher(exp, callback, debugName, location); |
154 case _WatcherType.HASH_MAP: | 200 case _WatcherType.HASH_MAP: |
155 return new _HashMapWatcher(exp, callback, debugName); | 201 return new _HashMapWatcher(exp, callback, debugName, location); |
156 default: | 202 default: |
157 return new _Watcher(exp, callback, debugName); | 203 return new _Watcher(exp, callback, debugName, location); |
158 } | 204 } |
159 } | 205 } |
160 | 206 |
161 /** | 207 /** |
162 * Add a watcher for [exp] and immediatly invoke [callback]. The watch event | 208 * Add a watcher for [exp] and immediatly invoke [callback]. The watch event |
163 * passed to [callback] will have `null` as the old value, and the current | 209 * passed to [callback] will have `null` as the old value, and the current |
164 * evaluation of [exp] as the new value. | 210 * evaluation of [exp] as the new value. |
165 */ | 211 */ |
166 ChangeUnobserver watchAndInvoke(exp, callback, [debugName]) { | 212 ChangeUnobserver watchAndInvoke(exp, ChangeObserver callback, |
167 var res = watch(exp, callback, debugName); | 213 [String debugName, String location]) { |
| 214 var res = watch(exp, callback, debugName, location); |
168 // TODO(jmesserly): this should be "is Getter" once dart2js bug is fixed. | 215 // TODO(jmesserly): this should be "is Getter" once dart2js bug is fixed. |
169 | 216 |
170 var value = exp; | 217 var value = exp; |
171 if (value is Function) { | 218 if (value is Function) { |
172 value = value(); | 219 value = value(); |
173 } | 220 } |
174 if (value is Iterable && value is! List) { | 221 if (value is Iterable && value is! List) { |
175 // TODO(jmesserly): we do this for compat with watch and observe, see the | 222 // TODO(jmesserly): we do this for compat with watch and observe, see the |
176 // respective methods. | 223 // respective methods. |
177 value = value.toList(); | 224 value = value.toList(); |
178 } | 225 } |
179 callback(new ChangeNotification(null, value)); | 226 callback(new ChangeNotification(null, value)); |
180 return res; | 227 return res; |
181 } | 228 } |
182 | 229 |
183 /** Internal set of active watchers. */ | 230 /** Internal set of active watchers. */ |
184 LinkedList<_Watcher> _watchers; | 231 LinkedList<_Watcher> _watchers; |
185 | 232 |
186 /** | 233 /** |
187 * An internal representation of a watcher. Contains the expression it watches, | 234 * An internal representation of a watcher. Contains the expression it watches, |
188 * the last value seen for it, and a callback to invoke when a change is | 235 * the last value seen for it, and a callback to invoke when a change is |
189 * detected. | 236 * detected. |
190 */ | 237 */ |
191 class _Watcher { | 238 class _Watcher { |
192 /** Name used to debug. */ | 239 /** Name used to debug. */ |
193 final String debugName; | 240 final String debugName; |
194 | 241 |
| 242 /** Location where the watcher was first installed (for debugging). */ |
| 243 String location; |
| 244 |
| 245 /** Unique id used for debugging purposes. */ |
| 246 final int _uniqueId; |
| 247 static int _nextId = 0; |
| 248 |
195 /** Function that retrieves the value being watched. */ | 249 /** Function that retrieves the value being watched. */ |
196 final Getter _getter; | 250 final Getter _getter; |
197 | 251 |
198 /** Callback to invoke when the value changes. */ | 252 /** Callback to invoke when the value changes. */ |
199 final ChangeObserver _callback; | 253 final ChangeObserver _callback; |
200 | 254 |
201 /** Last value observed on the matched expression. */ | 255 /** Last value observed on the matched expression. */ |
202 var _lastValue; | 256 var _lastValue; |
203 | 257 |
204 _Watcher(this._getter, this._callback, this.debugName) { | 258 _Watcher(this._getter, this._callback, this.debugName, this.location) |
| 259 : _uniqueId = _nextId++ { |
205 _lastValue = _getter(); | 260 _lastValue = _getter(); |
206 } | 261 } |
207 | 262 |
208 String toString() => debugName == null ? '<unnamed>' : debugName; | 263 String toString() => '$debugName (id: #$_uniqueId)'; |
209 | 264 |
210 /** Detect if any changes occurred and if so invoke [_callback]. */ | 265 /** Detect if any changes occurred and if so invoke [_callback]. */ |
211 bool compareAndNotify() { | 266 bool compareAndNotify() { |
212 var currentValue = _safeRead(); | 267 var currentValue = _safeRead(); |
213 if (_compare(currentValue)) { | 268 if (_compare(currentValue)) { |
214 var oldValue = _lastValue; | 269 var oldValue = _lastValue; |
215 _update(currentValue); | 270 _update(currentValue); |
216 _callback(new ChangeNotification(oldValue, currentValue)); | 271 _callback(new ChangeNotification(oldValue, currentValue)); |
217 return true; | 272 return true; |
218 } | 273 } |
219 return false; | 274 return false; |
220 } | 275 } |
221 | 276 |
222 bool _compare(currentValue) => _lastValue != currentValue; | 277 bool _compare(currentValue) => _lastValue != currentValue; |
223 | 278 |
224 void _update(currentValue) { | 279 void _update(currentValue) { |
| 280 if (verboseDebugMessages) { |
| 281 if (location != null) { |
| 282 _logger.info('watcher updated: $this, defined at:\n$location'); |
| 283 location = null; |
| 284 } else { |
| 285 _logger.info('watcher updated: $this'); |
| 286 } |
| 287 } |
225 _lastValue = currentValue; | 288 _lastValue = currentValue; |
226 } | 289 } |
227 | 290 |
228 /** Read [_getter] but detect whether exceptions were thrown. */ | 291 /** Read [_getter] but detect whether exceptions were thrown. */ |
229 _safeRead() { | 292 _safeRead() { |
230 try { | 293 try { |
231 return _getter(); | 294 return _getter(); |
232 } catch (e, trace) { | 295 } catch (e, trace) { |
233 print('error: evaluating $this watcher threw an exception ($e, $trace)'); | 296 _logger.warning('$this watcher threw an exception: $e, $trace'); |
234 } | 297 } |
235 return _lastValue; | 298 return _lastValue; |
236 } | 299 } |
237 } | 300 } |
238 | 301 |
239 /** Bound for the [dispatch] algorithm. */ | 302 /** Bound for the [dispatch] algorithm. */ |
240 final int _maxIter = 10; | 303 final int maxNumIterations = 10; |
241 | 304 |
242 /** | 305 /** |
243 * Scan all registered watchers and invoke their callbacks if the watched value | 306 * Scan all registered watchers and invoke their callbacks if the watched value |
244 * has changed. Because we allow listeners to modify other watched expressions, | 307 * has changed. Because we allow listeners to modify other watched expressions, |
245 * [dispatch] will reiterate until no changes occur or until we reach a | 308 * [dispatch] will reiterate until no changes occur or until we reach a |
246 * particular limit (10) to ensure termination. | 309 * particular limit (10) to ensure termination. |
247 */ | 310 */ |
248 void dispatch() { | 311 void dispatch() { |
249 if (_watchers == null) return; | 312 if (_watchers == null) return; |
250 bool dirty; | 313 bool dirty; |
251 int total = 0; | 314 int total = 0; |
252 do { | 315 do { |
253 dirty = false; | 316 dirty = false; |
254 for (var watcher in _watchers) { | 317 for (var watcher in _watchers) { |
255 // Get the next node just in case this node gets remove by the watcher | 318 // Get the next node just in case this node gets remove by the watcher |
256 if (watcher.compareAndNotify()) { | 319 if (watcher.compareAndNotify()) { |
257 dirty = true; | 320 dirty = true; |
258 } | 321 } |
259 } | 322 } |
260 } while (dirty && ++total < _maxIter); | 323 } while (dirty && ++total < maxNumIterations); |
261 if (total == _maxIter) { | 324 if (total == maxNumIterations) { |
262 print('Possible loop in watchers propagation, stopped dispatch.'); | 325 _logger.warning('Possible loop in watchers propagation, stopped dispatch.'); |
263 } | 326 } |
264 } | 327 } |
265 | 328 |
266 /** | 329 /** |
267 * An indirect getter. Basically a simple closure that returns a value, which is | 330 * An indirect getter. Basically a simple closure that returns a value, which is |
268 * the most common argument in [watch]. | 331 * the most common argument in [watch]. |
269 */ | 332 */ |
270 typedef T Getter<T>(); | 333 typedef T Getter<T>(); |
271 | 334 |
272 /** An indirect setter. */ | 335 /** An indirect setter. */ |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
314 } | 377 } |
315 } | 378 } |
316 } | 379 } |
317 | 380 |
318 /** | 381 /** |
319 * A watcher for list objects. It stores as the last value a shallow copy of the | 382 * A watcher for list objects. It stores as the last value a shallow copy of the |
320 * list as it was when we last detected any changes. | 383 * list as it was when we last detected any changes. |
321 */ | 384 */ |
322 class _ListWatcher<T> extends _Watcher { | 385 class _ListWatcher<T> extends _Watcher { |
323 | 386 |
324 _ListWatcher(getter, ChangeObserver callback, String debugName) | 387 _ListWatcher(getter, ChangeObserver callback, String debugName, |
325 : super(getter, callback, debugName) { | 388 String location) |
| 389 : super(getter, callback, debugName, location) { |
326 _update(_safeRead()); | 390 _update(_safeRead()); |
327 } | 391 } |
328 | 392 |
329 bool _compare(List<T> currentValue) { | 393 bool _compare(List<T> currentValue) { |
330 return _iterablesNotEqual(_lastValue, currentValue); | 394 return _iterablesNotEqual(_lastValue, currentValue); |
331 } | 395 } |
332 | 396 |
333 void _update(currentValue) { | 397 void _update(currentValue) { |
334 _lastValue = new List<T>.from(currentValue); | 398 _lastValue = new List<T>.from(currentValue); |
335 } | 399 } |
336 } | 400 } |
337 | 401 |
338 /** | 402 /** |
339 * A watcher for hash map objects. It stores as the last value a shallow copy | 403 * A watcher for hash map objects. It stores as the last value a shallow copy |
340 * of the map as it was when we last detected any changes. Order for the map | 404 * of the map as it was when we last detected any changes. Order for the map |
341 * does not matter for equality. | 405 * does not matter for equality. |
342 */ | 406 */ |
343 class _HashMapWatcher<K, V> extends _Watcher { | 407 class _HashMapWatcher<K, V> extends _Watcher { |
344 | 408 |
345 _HashMapWatcher(getter, ChangeObserver callback, String debugName) | 409 _HashMapWatcher(getter, ChangeObserver callback, String debugName, |
346 : super(getter, callback, debugName) { | 410 String location) |
| 411 : super(getter, callback, debugName, location) { |
347 _update(_safeRead()); | 412 _update(_safeRead()); |
348 } | 413 } |
349 | 414 |
350 bool _compare(Map<K, V> currentValue) { | 415 bool _compare(Map<K, V> currentValue) { |
351 Iterable<K> keys = _lastValue.keys; | 416 Iterable<K> keys = _lastValue.keys; |
352 if (keys.length != currentValue.keys.length) return true; | 417 if (keys.length != currentValue.keys.length) return true; |
353 | 418 |
354 Iterator<K> keyIterator = keys.iterator; | 419 Iterator<K> keyIterator = keys.iterator; |
355 while (keyIterator.moveNext()) { | 420 while (keyIterator.moveNext()) { |
356 K key = keyIterator.current; | 421 K key = keyIterator.current; |
357 if (!currentValue.containsKey(key)) return true; | 422 if (!currentValue.containsKey(key)) return true; |
358 if (_lastValue[key] != currentValue[key]) return true; | 423 if (_lastValue[key] != currentValue[key]) return true; |
359 } | 424 } |
360 return false; | 425 return false; |
361 } | 426 } |
362 | 427 |
363 void _update(currentValue) { | 428 void _update(currentValue) { |
364 _lastValue = new Map<K, V>.from(currentValue); | 429 _lastValue = new Map<K, V>.from(currentValue); |
365 } | 430 } |
366 } | 431 } |
367 | 432 |
368 /** | 433 /** |
369 * A watcher for maps where key order matters. It stores as the last value a | 434 * A watcher for maps where key order matters. It stores as the last value a |
370 * shallow copy of the map as it was when we last detected any changes. | 435 * shallow copy of the map as it was when we last detected any changes. |
371 */ | 436 */ |
372 class _OrderDependantMapWatcher<K, V> extends _Watcher { | 437 class _OrderDependantMapWatcher<K, V> extends _Watcher { |
373 | 438 |
374 _OrderDependantMapWatcher(getter, ChangeObserver callback, String debugName) | 439 _OrderDependantMapWatcher(getter, ChangeObserver callback, String debugName, |
375 : super(getter, callback, debugName) { | 440 String location) |
| 441 : super(getter, callback, debugName, location) { |
376 _update(_safeRead()); | 442 _update(_safeRead()); |
377 } | 443 } |
378 | 444 |
379 bool _compare(Map<K, V> currentValue) { | 445 bool _compare(Map<K, V> currentValue) { |
380 return _iterablesNotEqual(currentValue.keys, _lastValue.keys) || | 446 return _iterablesNotEqual(currentValue.keys, _lastValue.keys) || |
381 _iterablesNotEqual(currentValue.values, _lastValue.values); | 447 _iterablesNotEqual(currentValue.values, _lastValue.values); |
382 } | 448 } |
383 | 449 |
384 void _update(currentValue) { | 450 void _update(currentValue) { |
385 _lastValue = new LinkedHashMap.from(currentValue); | 451 _lastValue = new LinkedHashMap.from(currentValue); |
(...skipping 19 matching lines...) Expand all Loading... |
405 class _WatcherType { | 471 class _WatcherType { |
406 final _value; | 472 final _value; |
407 const _WatcherType._internal(this._value); | 473 const _WatcherType._internal(this._value); |
408 toString() => 'Enum.$_value'; | 474 toString() => 'Enum.$_value'; |
409 | 475 |
410 static const LIST = const _WatcherType._internal('LIST'); | 476 static const LIST = const _WatcherType._internal('LIST'); |
411 static const HASH_MAP = const _WatcherType._internal('HASH_MAP'); | 477 static const HASH_MAP = const _WatcherType._internal('HASH_MAP'); |
412 static const ORDERED_MAP = const _WatcherType._internal('ORDERED_MAP'); | 478 static const ORDERED_MAP = const _WatcherType._internal('ORDERED_MAP'); |
413 static const OTHER = const _WatcherType._internal('OTHER'); | 479 static const OTHER = const _WatcherType._internal('OTHER'); |
414 } | 480 } |
OLD | NEW |