Chromium Code Reviews| 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 |