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 * Log for messages produced at runtime by this library. Logging can be | |
78 * configured by accessing Logger.root from the logging library. | |
79 */ | |
80 final Logger _logger = new Logger('watcher'); | |
81 | |
82 /** | |
68 * Watch for changes in [target]. The [callback] function will be called when | 83 * 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 | 84 * [dispatch] is called and the value represented by [target] had changed. The |
70 * returned function can be used to unregister this watcher. | 85 * returned function can be used to unregister this watcher. |
71 * | 86 * |
72 * There are several values you can use for [target]: | 87 * There are several values you can use for [target]: |
73 * | 88 * |
74 * * A [Getter] function. | 89 * * A [Getter] function. |
75 * Use this to watch expressions as they change. For instance, to watch | 90 * 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: | 91 * whether `a.b.c` changes, wrap it in a getter and call [watch] as follows: |
77 * watch(() => a.b.c, ...) | 92 * watch(() => a.b.c, ...) |
78 * These targets are tracked to check for equality. If calling `target()` | 93 * 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 | 94 * 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 | 95 * 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]. | 96 * special way, similar to passing [List] directly as [target]. |
82 * **Important**: this library assumes that [Getter] is a read-only function | 97 * **Important**: this library assumes that [Getter] is a read-only function |
83 * and that it will consistently return the same value if called multiple | 98 * and that it will consistently return the same value if called multiple |
84 * times in a row. | 99 * times in a row. |
85 * | 100 * |
86 * * A [List]. | 101 * * A [List]. |
87 * Use this to watch whether a list object changes. For instance, to detect | 102 * 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: | 103 * if an element is added or changed in a list can call [watch] as follows: |
89 * watch(list, ...) | 104 * watch(list, ...) |
90 * | 105 * |
91 * * A [Handle]. | 106 * * A [Handle]. |
92 * This is syntactic sugar for using the getter portion of a [Handle]. | 107 * This is syntactic sugar for using the getter portion of a [Handle]. |
93 * watch(handle, ...) // equivalent to `watch(handle._getter, ...)` | 108 * watch(handle, ...) // equivalent to `watch(handle._getter, ...)` |
94 */ | 109 */ |
95 ChangeUnobserver watch(target, ChangeObserver callback, [String debugName]) { | 110 ChangeUnobserver watch(target, ChangeObserver callback, |
111 [String debugName, String location]) { | |
96 if (useObservers) return observe(target, callback); | 112 if (useObservers) return observe(target, callback); |
97 | 113 |
98 if (callback == null) return () {}; // no use in passing null as a callback. | 114 if (callback == null) return () {}; // no use in passing null as a callback. |
99 if (_watchers == null) _watchers = new LinkedList<_Watcher>(); | 115 if (_watchers == null) _watchers = new LinkedList<_Watcher>(); |
116 debugName = debugName == null ? '<unnamed>' : debugName; | |
100 Function exp; | 117 Function exp; |
101 _WatcherType watcherType = _WatcherType.OTHER; | 118 _WatcherType watcherType = _WatcherType.OTHER; |
102 if (target is Handle) { | 119 if (target is Handle) { |
103 exp = (target as Handle)._getter; | 120 exp = (target as Handle)._getter; |
104 } else if (target is Function) { | 121 } else if (target is Function) { |
105 exp = target; | 122 exp = target; |
106 try { | 123 try { |
107 var val = target(); | 124 var val = target(); |
108 if (val is List) { | 125 if (val is List) { |
109 watcherType = _WatcherType.LIST; | 126 watcherType = _WatcherType.LIST; |
110 } else if (val is Iterable) { | 127 } else if (val is Iterable) { |
111 watcherType = _WatcherType.LIST; | 128 watcherType = _WatcherType.LIST; |
112 exp = () => target().toList(); | 129 exp = () => target().toList(); |
113 } else if ((val is LinkedHashMap) || (val is SplayTreeMap)) { | 130 } else if ((val is LinkedHashMap) || (val is SplayTreeMap)) { |
114 watcherType = _WatcherType.ORDERED_MAP; | 131 watcherType = _WatcherType.ORDERED_MAP; |
115 } else if (val is Map) { | 132 } else if (val is Map) { |
116 watcherType = _WatcherType.HASH_MAP; | 133 watcherType = _WatcherType.HASH_MAP; |
117 } | 134 } |
118 } catch (e, trace) { // in case target() throws some error | 135 } catch (e, trace) { // in case target() throws some error |
119 // TODO(sigmund): use logging instead of print when logger is in the SDK | 136 _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 } | 137 } |
124 } else if (target is List) { | 138 } else if (target is List) { |
125 exp = () => target; | 139 exp = () => target; |
126 watcherType = _WatcherType.LIST; | 140 watcherType = _WatcherType.LIST; |
127 } else if (target is Iterable) { | 141 } else if (target is Iterable) { |
128 exp = () => target.toList(); | 142 exp = () => target.toList(); |
129 watcherType = _WatcherType.LIST; | 143 watcherType = _WatcherType.LIST; |
130 } else if ((target is LinkedHashMap) || (target is SplayTreeMap)) { | 144 } else if ((target is LinkedHashMap) || (target is SplayTreeMap)) { |
131 exp = () => target; | 145 exp = () => target; |
132 watcherType = _WatcherType.ORDERED_MAP; | 146 watcherType = _WatcherType.ORDERED_MAP; |
133 } else if (target is Map) { | 147 } else if (target is Map) { |
134 exp = () => target; | 148 exp = () => target; |
135 watcherType = _WatcherType.HASH_MAP; | 149 watcherType = _WatcherType.HASH_MAP; |
136 } | 150 } |
137 | 151 |
138 var watcher = _createWatcher(watcherType, exp, callback, debugName); | 152 if (verboseDebugMessages && location == null) { |
153 // Record the current stack trace to help diagnoze loops. | |
154 try { throw ""; } catch (e, trace) { | |
Jennifer Messerly
2013/07/11 21:27:23
use _readCurrentStackTrace?
Siggi Cherem (dart-lang)
2013/07/11 22:07:01
Done.
| |
155 location = trace.toString(); | |
156 } | |
157 } | |
158 | |
159 var watcher = _createWatcher(watcherType, exp, callback, debugName, location); | |
139 var node = _watchers.add(watcher); | 160 var node = _watchers.add(watcher); |
140 return node.remove; | 161 return node.remove; |
141 } | 162 } |
142 | 163 |
143 /** | 164 /** |
144 * Creates a watcher for [exp] of [type] with [callback] function and | 165 * Creates a watcher for [exp] of [type] with [callback] function and |
145 * [debugName]. | 166 * [debugName]. |
146 */ | 167 */ |
147 _Watcher _createWatcher(_WatcherType type, Function exp, | 168 _Watcher _createWatcher(_WatcherType type, Function exp, |
148 ChangeObserver callback, String debugName) { | 169 ChangeObserver callback, String debugName, String location) { |
149 switch(type) { | 170 switch(type) { |
150 case _WatcherType.LIST: | 171 case _WatcherType.LIST: |
151 return new _ListWatcher(exp, callback, debugName); | 172 return new _ListWatcher(exp, callback, debugName, location); |
152 case _WatcherType.ORDERED_MAP: | 173 case _WatcherType.ORDERED_MAP: |
153 return new _OrderDependantMapWatcher(exp, callback, debugName); | 174 return new _OrderDependantMapWatcher(exp, callback, debugName, location); |
154 case _WatcherType.HASH_MAP: | 175 case _WatcherType.HASH_MAP: |
155 return new _HashMapWatcher(exp, callback, debugName); | 176 return new _HashMapWatcher(exp, callback, debugName, location); |
156 default: | 177 default: |
157 return new _Watcher(exp, callback, debugName); | 178 return new _Watcher(exp, callback, debugName, location); |
158 } | 179 } |
159 } | 180 } |
160 | 181 |
161 /** | 182 /** |
162 * Add a watcher for [exp] and immediatly invoke [callback]. The watch event | 183 * 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 | 184 * passed to [callback] will have `null` as the old value, and the current |
164 * evaluation of [exp] as the new value. | 185 * evaluation of [exp] as the new value. |
165 */ | 186 */ |
166 ChangeUnobserver watchAndInvoke(exp, callback, [debugName]) { | 187 ChangeUnobserver watchAndInvoke(exp, ChangeObserver callback, |
167 var res = watch(exp, callback, debugName); | 188 [String debugName, String location]) { |
189 var res = watch(exp, callback, debugName, location); | |
168 // TODO(jmesserly): this should be "is Getter" once dart2js bug is fixed. | 190 // TODO(jmesserly): this should be "is Getter" once dart2js bug is fixed. |
169 | 191 |
170 var value = exp; | 192 var value = exp; |
171 if (value is Function) { | 193 if (value is Function) { |
172 value = value(); | 194 value = value(); |
173 } | 195 } |
174 if (value is Iterable && value is! List) { | 196 if (value is Iterable && value is! List) { |
175 // TODO(jmesserly): we do this for compat with watch and observe, see the | 197 // TODO(jmesserly): we do this for compat with watch and observe, see the |
176 // respective methods. | 198 // respective methods. |
177 value = value.toList(); | 199 value = value.toList(); |
178 } | 200 } |
179 callback(new ChangeNotification(null, value)); | 201 callback(new ChangeNotification(null, value)); |
180 return res; | 202 return res; |
181 } | 203 } |
182 | 204 |
183 /** Internal set of active watchers. */ | 205 /** Internal set of active watchers. */ |
184 LinkedList<_Watcher> _watchers; | 206 LinkedList<_Watcher> _watchers; |
185 | 207 |
186 /** | 208 /** |
187 * An internal representation of a watcher. Contains the expression it watches, | 209 * 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 | 210 * the last value seen for it, and a callback to invoke when a change is |
189 * detected. | 211 * detected. |
190 */ | 212 */ |
191 class _Watcher { | 213 class _Watcher { |
192 /** Name used to debug. */ | 214 /** Name used to debug. */ |
193 final String debugName; | 215 final String debugName; |
194 | 216 |
217 /** Location where the watcher was first installed (for debugging). */ | |
218 String location; | |
219 | |
220 /** Unique id used for debugging purposes. */ | |
221 final int _uniqueId; | |
222 static int _nextId = 0; | |
223 | |
195 /** Function that retrieves the value being watched. */ | 224 /** Function that retrieves the value being watched. */ |
196 final Getter _getter; | 225 final Getter _getter; |
197 | 226 |
198 /** Callback to invoke when the value changes. */ | 227 /** Callback to invoke when the value changes. */ |
199 final ChangeObserver _callback; | 228 final ChangeObserver _callback; |
200 | 229 |
201 /** Last value observed on the matched expression. */ | 230 /** Last value observed on the matched expression. */ |
202 var _lastValue; | 231 var _lastValue; |
203 | 232 |
204 _Watcher(this._getter, this._callback, this.debugName) { | 233 _Watcher(this._getter, this._callback, this.debugName, this.location) |
234 : _uniqueId = _nextId++ { | |
205 _lastValue = _getter(); | 235 _lastValue = _getter(); |
206 } | 236 } |
207 | 237 |
208 String toString() => debugName == null ? '<unnamed>' : debugName; | 238 String toString() => '$debugName (id: #$_uniqueId)'; |
209 | 239 |
210 /** Detect if any changes occurred and if so invoke [_callback]. */ | 240 /** Detect if any changes occurred and if so invoke [_callback]. */ |
211 bool compareAndNotify() { | 241 bool compareAndNotify() { |
212 var currentValue = _safeRead(); | 242 var currentValue = _safeRead(); |
213 if (_compare(currentValue)) { | 243 if (_compare(currentValue)) { |
214 var oldValue = _lastValue; | 244 var oldValue = _lastValue; |
215 _update(currentValue); | 245 _update(currentValue); |
216 _callback(new ChangeNotification(oldValue, currentValue)); | 246 _callback(new ChangeNotification(oldValue, currentValue)); |
217 return true; | 247 return true; |
218 } | 248 } |
219 return false; | 249 return false; |
220 } | 250 } |
221 | 251 |
222 bool _compare(currentValue) => _lastValue != currentValue; | 252 bool _compare(currentValue) => _lastValue != currentValue; |
223 | 253 |
224 void _update(currentValue) { | 254 void _update(currentValue) { |
255 if (verboseDebugMessages) { | |
256 if (location != null) { | |
257 _logger.info('watcher updated: $this, defined at:\n$location'); | |
258 location = null; | |
259 } else { | |
260 _logger.info('watcher updated: $this'); | |
261 } | |
262 } | |
225 _lastValue = currentValue; | 263 _lastValue = currentValue; |
226 } | 264 } |
227 | 265 |
228 /** Read [_getter] but detect whether exceptions were thrown. */ | 266 /** Read [_getter] but detect whether exceptions were thrown. */ |
229 _safeRead() { | 267 _safeRead() { |
230 try { | 268 try { |
231 return _getter(); | 269 return _getter(); |
232 } catch (e, trace) { | 270 } catch (e, trace) { |
233 print('error: evaluating $this watcher threw an exception ($e, $trace)'); | 271 _logger.warning('$this watcher threw an exception: $e, $trace'); |
234 } | 272 } |
235 return _lastValue; | 273 return _lastValue; |
236 } | 274 } |
237 } | 275 } |
238 | 276 |
239 /** Bound for the [dispatch] algorithm. */ | 277 /** Bound for the [dispatch] algorithm. */ |
240 final int _maxIter = 10; | 278 final int maxNumIterations = 10; |
241 | 279 |
242 /** | 280 /** |
243 * Scan all registered watchers and invoke their callbacks if the watched value | 281 * Scan all registered watchers and invoke their callbacks if the watched value |
244 * has changed. Because we allow listeners to modify other watched expressions, | 282 * has changed. Because we allow listeners to modify other watched expressions, |
245 * [dispatch] will reiterate until no changes occur or until we reach a | 283 * [dispatch] will reiterate until no changes occur or until we reach a |
246 * particular limit (10) to ensure termination. | 284 * particular limit (10) to ensure termination. |
247 */ | 285 */ |
248 void dispatch() { | 286 void dispatch() { |
249 if (_watchers == null) return; | 287 if (_watchers == null) return; |
250 bool dirty; | 288 bool dirty; |
251 int total = 0; | 289 int total = 0; |
252 do { | 290 do { |
253 dirty = false; | 291 dirty = false; |
254 for (var watcher in _watchers) { | 292 for (var watcher in _watchers) { |
255 // Get the next node just in case this node gets remove by the watcher | 293 // Get the next node just in case this node gets remove by the watcher |
256 if (watcher.compareAndNotify()) { | 294 if (watcher.compareAndNotify()) { |
257 dirty = true; | 295 dirty = true; |
258 } | 296 } |
259 } | 297 } |
260 } while (dirty && ++total < _maxIter); | 298 } while (dirty && ++total < maxNumIterations); |
261 if (total == _maxIter) { | 299 if (total == maxNumIterations) { |
262 print('Possible loop in watchers propagation, stopped dispatch.'); | 300 _logger.warning('Possible loop in watchers propagation, stopped dispatch.'); |
263 } | 301 } |
264 } | 302 } |
265 | 303 |
266 /** | 304 /** |
267 * An indirect getter. Basically a simple closure that returns a value, which is | 305 * An indirect getter. Basically a simple closure that returns a value, which is |
268 * the most common argument in [watch]. | 306 * the most common argument in [watch]. |
269 */ | 307 */ |
270 typedef T Getter<T>(); | 308 typedef T Getter<T>(); |
271 | 309 |
272 /** An indirect setter. */ | 310 /** An indirect setter. */ |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
314 } | 352 } |
315 } | 353 } |
316 } | 354 } |
317 | 355 |
318 /** | 356 /** |
319 * A watcher for list objects. It stores as the last value a shallow copy of the | 357 * 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. | 358 * list as it was when we last detected any changes. |
321 */ | 359 */ |
322 class _ListWatcher<T> extends _Watcher { | 360 class _ListWatcher<T> extends _Watcher { |
323 | 361 |
324 _ListWatcher(getter, ChangeObserver callback, String debugName) | 362 _ListWatcher(getter, ChangeObserver callback, String debugName, |
325 : super(getter, callback, debugName) { | 363 String location) |
364 : super(getter, callback, debugName, location) { | |
326 _update(_safeRead()); | 365 _update(_safeRead()); |
327 } | 366 } |
328 | 367 |
329 bool _compare(List<T> currentValue) { | 368 bool _compare(List<T> currentValue) { |
330 return _iterablesNotEqual(_lastValue, currentValue); | 369 return _iterablesNotEqual(_lastValue, currentValue); |
331 } | 370 } |
332 | 371 |
333 void _update(currentValue) { | 372 void _update(currentValue) { |
334 _lastValue = new List<T>.from(currentValue); | 373 _lastValue = new List<T>.from(currentValue); |
335 } | 374 } |
336 } | 375 } |
337 | 376 |
338 /** | 377 /** |
339 * A watcher for hash map objects. It stores as the last value a shallow copy | 378 * 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 | 379 * of the map as it was when we last detected any changes. Order for the map |
341 * does not matter for equality. | 380 * does not matter for equality. |
342 */ | 381 */ |
343 class _HashMapWatcher<K, V> extends _Watcher { | 382 class _HashMapWatcher<K, V> extends _Watcher { |
344 | 383 |
345 _HashMapWatcher(getter, ChangeObserver callback, String debugName) | 384 _HashMapWatcher(getter, ChangeObserver callback, String debugName, |
346 : super(getter, callback, debugName) { | 385 String location) |
386 : super(getter, callback, debugName, location) { | |
347 _update(_safeRead()); | 387 _update(_safeRead()); |
348 } | 388 } |
349 | 389 |
350 bool _compare(Map<K, V> currentValue) { | 390 bool _compare(Map<K, V> currentValue) { |
351 Iterable<K> keys = _lastValue.keys; | 391 Iterable<K> keys = _lastValue.keys; |
352 if (keys.length != currentValue.keys.length) return true; | 392 if (keys.length != currentValue.keys.length) return true; |
353 | 393 |
354 Iterator<K> keyIterator = keys.iterator; | 394 Iterator<K> keyIterator = keys.iterator; |
355 while (keyIterator.moveNext()) { | 395 while (keyIterator.moveNext()) { |
356 K key = keyIterator.current; | 396 K key = keyIterator.current; |
357 if (!currentValue.containsKey(key)) return true; | 397 if (!currentValue.containsKey(key)) return true; |
358 if (_lastValue[key] != currentValue[key]) return true; | 398 if (_lastValue[key] != currentValue[key]) return true; |
359 } | 399 } |
360 return false; | 400 return false; |
361 } | 401 } |
362 | 402 |
363 void _update(currentValue) { | 403 void _update(currentValue) { |
364 _lastValue = new Map<K, V>.from(currentValue); | 404 _lastValue = new Map<K, V>.from(currentValue); |
365 } | 405 } |
366 } | 406 } |
367 | 407 |
368 /** | 408 /** |
369 * A watcher for maps where key order matters. It stores as the last value a | 409 * 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. | 410 * shallow copy of the map as it was when we last detected any changes. |
371 */ | 411 */ |
372 class _OrderDependantMapWatcher<K, V> extends _Watcher { | 412 class _OrderDependantMapWatcher<K, V> extends _Watcher { |
373 | 413 |
374 _OrderDependantMapWatcher(getter, ChangeObserver callback, String debugName) | 414 _OrderDependantMapWatcher(getter, ChangeObserver callback, String debugName, |
375 : super(getter, callback, debugName) { | 415 String location) |
416 : super(getter, callback, debugName, location) { | |
376 _update(_safeRead()); | 417 _update(_safeRead()); |
377 } | 418 } |
378 | 419 |
379 bool _compare(Map<K, V> currentValue) { | 420 bool _compare(Map<K, V> currentValue) { |
380 return _iterablesNotEqual(currentValue.keys, _lastValue.keys) || | 421 return _iterablesNotEqual(currentValue.keys, _lastValue.keys) || |
381 _iterablesNotEqual(currentValue.values, _lastValue.values); | 422 _iterablesNotEqual(currentValue.values, _lastValue.values); |
382 } | 423 } |
383 | 424 |
384 void _update(currentValue) { | 425 void _update(currentValue) { |
385 _lastValue = new LinkedHashMap.from(currentValue); | 426 _lastValue = new LinkedHashMap.from(currentValue); |
(...skipping 19 matching lines...) Expand all Loading... | |
405 class _WatcherType { | 446 class _WatcherType { |
406 final _value; | 447 final _value; |
407 const _WatcherType._internal(this._value); | 448 const _WatcherType._internal(this._value); |
408 toString() => 'Enum.$_value'; | 449 toString() => 'Enum.$_value'; |
409 | 450 |
410 static const LIST = const _WatcherType._internal('LIST'); | 451 static const LIST = const _WatcherType._internal('LIST'); |
411 static const HASH_MAP = const _WatcherType._internal('HASH_MAP'); | 452 static const HASH_MAP = const _WatcherType._internal('HASH_MAP'); |
412 static const ORDERED_MAP = const _WatcherType._internal('ORDERED_MAP'); | 453 static const ORDERED_MAP = const _WatcherType._internal('ORDERED_MAP'); |
413 static const OTHER = const _WatcherType._internal('OTHER'); | 454 static const OTHER = const _WatcherType._internal('OTHER'); |
414 } | 455 } |
OLD | NEW |