@@ -2,6 +2,9 @@ library dirty_checking_change_detector;
2
2
3
3
import 'dart:collection' ;
4
4
import 'package:angular/change_detection/change_detection.dart' ;
5
+ import 'dart:async' ;
6
+ import 'package:observe/observe.dart' as obs;
7
+ import 'package:smoke/smoke.dart' as smoke;
5
8
6
9
/**
7
10
* [DirtyCheckingChangeDetector] determines which object properties have changed
@@ -46,6 +49,9 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
46
49
*/
47
50
DirtyCheckingRecord _recordHead, _recordTail;
48
51
52
+ /// Registry of record with their notifiers
53
+ Map <DirtyCheckingRecord <H >, StreamSubscription > _observableRecords;
54
+
49
55
/**
50
56
* Same as [_tail] but includes child-group records as well.
51
57
*/
@@ -116,8 +122,7 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
116
122
117
123
WatchRecord <H > watch (Object object, String field, H handler) {
118
124
assert (_root != null ); // prove that we are not deleted connected;
119
- return _recordAdd (new DirtyCheckingRecord (this , _fieldGetterFactory,
120
- handler, field, object));
125
+ return _recordAdd (new DirtyCheckingRecord (this , _fieldGetterFactory, handler, field, object));
121
126
}
122
127
123
128
/**
@@ -146,6 +151,7 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
146
151
var root;
147
152
assert ((root = _root) != null );
148
153
assert (root._assertRecordsOk ());
154
+ _cancelChildObservables ();
149
155
DirtyCheckingRecord prevRecord = _recordHead._prevRecord;
150
156
var childInclRecordTail = _childInclRecordTail;
151
157
DirtyCheckingRecord nextRecord = childInclRecordTail._nextRecord;
@@ -191,6 +197,8 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
191
197
}
192
198
193
199
void _recordRemove (DirtyCheckingRecord record) {
200
+ _cancelObservable (record);
201
+
194
202
DirtyCheckingRecord previous = record._prevRecord;
195
203
DirtyCheckingRecord next = record._nextRecord;
196
204
@@ -209,6 +217,35 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
209
217
}
210
218
}
211
219
220
+ /// Register an observable object in this group.
221
+ void _registerObservable (DirtyCheckingRecord <H > record, StreamSubscription subscription) {
222
+ if (_observableRecords == null ) _observableRecords = new HashMap .identity ();
223
+ _observableRecords[record] = subscription;
224
+ }
225
+
226
+ /// Cancel an observable subscription in this group.
227
+ void _cancelObservable (DirtyCheckingRecord <H > record) {
228
+ if (_observableRecords == null ) return ;
229
+ var subscription = _observableRecords.remove (record);
230
+ if (subscription != null ) subscription.cancel ();
231
+ }
232
+
233
+ /// Cancel all observable subscriptions in this group.
234
+ void _cancelOwnObservables () {
235
+ if (_observableRecords != null ) {
236
+ _observableRecords.values.forEach ((s) => s.cancel ());
237
+ _observableRecords = null ;
238
+ }
239
+ }
240
+
241
+ /// Cancel all observable subscriptions in this group and descendant groups
242
+ void _cancelChildObservables () {
243
+ this ._cancelOwnObservables ();
244
+ for (var child = _childHead; child != null ; child = child._next) {
245
+ child._cancelChildObservables ();
246
+ }
247
+ }
248
+
212
249
String toString () {
213
250
var lines = [];
214
251
if (_parent == null ) {
@@ -306,6 +343,7 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H>
306
343
Iterator <Record <H >> collectChanges ({EvalExceptionHandler exceptionHandler,
307
344
AvgStopwatch stopwatch}) {
308
345
if (stopwatch != null ) stopwatch.start ();
346
+
309
347
DirtyCheckingRecord changeTail = _fakeHead;
310
348
DirtyCheckingRecord current = _recordHead; // current index
311
349
@@ -375,18 +413,26 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
375
413
'NOOP' ,
376
414
'IDENTITY' ,
377
415
'GETTER' ,
378
- 'GETTER / CLOSURE'
416
+ 'NOTIFIED GETTER' ,
417
+ 'GETTER / CLOSURE' ,
418
+ 'OBSERVABLE GETTER / CLOSURE' ,
379
419
'MAP[]' ,
380
420
'ITERABLE' ,
381
- 'MAP' ];
421
+ 'NOTIFIED LIST' ,
422
+ 'MAP' ,
423
+ 'NOTIFIED MAP' ];
382
424
static const int _MODE_MARKER_ = 0 ;
383
425
static const int _MODE_NOOP_ = 1 ;
384
426
static const int _MODE_IDENTITY_ = 2 ;
385
427
static const int _MODE_GETTER_ = 3 ;
386
- static const int _MODE_GETTER_OR_METHOD_CLOSURE_ = 4 ;
387
- static const int _MODE_MAP_FIELD_ = 5 ;
388
- static const int _MODE_ITERABLE_ = 6 ;
389
- static const int _MODE_MAP_ = 7 ;
428
+ static const int _MODE_GETTER_NOTIFIED_ = 4 ;
429
+ static const int _MODE_GETTER_OR_METHOD_CLOSURE_ = 5 ;
430
+ static const int _MODE_GETTER_OBS_OR_METHOD_CLOSURE_ = 6 ;
431
+ static const int _MODE_MAP_FIELD_ = 7 ;
432
+ static const int _MODE_ITERABLE_ = 8 ;
433
+ static const int _MODE_LIST_NOTIFIED_ = 9 ;
434
+ static const int _MODE_MAP_ = 10 ;
435
+ static const int _MODE_MAP_NOTIFIED_ = 11 ;
390
436
391
437
final DirtyCheckingChangeDetectorGroup _group;
392
438
final FieldGetterFactory _fieldGetterFactory;
@@ -403,8 +449,7 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
403
449
var _object;
404
450
FieldGetter _getter;
405
451
406
- DirtyCheckingRecord (this ._group, this ._fieldGetterFactory, this .handler,
407
- this .field, _object) {
452
+ DirtyCheckingRecord (this ._group, this ._fieldGetterFactory, this .handler, this .field, _object) {
408
453
object = _object;
409
454
}
410
455
@@ -424,7 +469,10 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
424
469
* reflection. If [Map] then it sets up map accessor.
425
470
*/
426
471
void set object (obj) {
472
+ _group._cancelObservable (this );
473
+
427
474
_object = obj;
475
+
428
476
if (obj == null ) {
429
477
_mode = _MODE_IDENTITY_ ;
430
478
_getter = null ;
@@ -434,30 +482,41 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
434
482
if (field == null ) {
435
483
_getter = null ;
436
484
if (obj is Map ) {
437
- if (_mode != _MODE_MAP_ ) {
438
- _mode = _MODE_MAP_ ;
485
+ if (currentValue is ! _MapChangeRecord ) {
439
486
currentValue = new _MapChangeRecord ();
440
- }
441
- if (currentValue.isDirty) {
442
- // We're dirty because the mapping we tracked by reference mutated.
443
- // In addition, our reference has now changed. We should compare the
444
- // previous reported value of that mapping with the one from the
445
- // new reference.
487
+ } else if (currentValue.isDirty) {
488
+ // We're dirty because the mapping we tracked by reference mutated. In addition, our
489
+ // reference has now changed. We should compare the previous reported value of that
490
+ // mapping with the one from the new reference.
446
491
currentValue._revertToPreviousState ();
447
492
}
448
-
493
+ if (obj is obs.ChangeNotifier ) {
494
+ _mode = _MODE_MAP_NOTIFIED_ ; // Run the dccd after the map is added
495
+ var subscription = (obj as obs.ChangeNotifier ).changes.listen ((_) {
496
+ _mode = _MODE_MAP_NOTIFIED_ ; // Run the dccd after the map is updated
497
+ });
498
+ _group._registerObservable (this , subscription);
499
+ } else {
500
+ _mode = _MODE_MAP_ ;
501
+ }
449
502
} else if (obj is Iterable ) {
450
- if (_mode != _MODE_ITERABLE_ ) {
451
- _mode = _MODE_ITERABLE_ ;
503
+ if (currentValue is ! _CollectionChangeRecord ) {
452
504
currentValue = new _CollectionChangeRecord ();
453
- }
454
- if (currentValue.isDirty) {
455
- // We're dirty because the collection we tracked by reference mutated.
456
- // In addition, our reference has now changed. We should compare the
457
- // previous reported value of that collection with the one from the
458
- // new reference.
505
+ } else if (currentValue.isDirty) {
506
+ // We're dirty because the collection we tracked by reference mutated. In addition, our
507
+ // reference has now changed. We should compare the previous reported value of that
508
+ // collection with the one from the new reference.
459
509
currentValue._revertToPreviousState ();
460
510
}
511
+ if (obj is obs.ObservableList ) {
512
+ _mode = _MODE_LIST_NOTIFIED_ ; // Run the dccd after the list is added
513
+ var subscription = (obj as obs.ObservableList ).listChanges.listen ((_) {
514
+ _mode = _MODE_LIST_NOTIFIED_ ; // Run the dccd after the list is updated
515
+ });
516
+ _group._registerObservable (this , subscription);
517
+ } else {
518
+ _mode = _MODE_ITERABLE_ ;
519
+ }
461
520
} else {
462
521
_mode = _MODE_IDENTITY_ ;
463
522
}
@@ -469,7 +528,9 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
469
528
_mode = _MODE_MAP_FIELD_ ;
470
529
_getter = null ;
471
530
} else {
472
- _mode = _MODE_GETTER_OR_METHOD_CLOSURE_ ;
531
+ _mode = obj is obs.ChangeNotifier ?
532
+ _MODE_GETTER_OBS_OR_METHOD_CLOSURE_ :
533
+ _MODE_GETTER_OR_METHOD_CLOSURE_ ;
473
534
_getter = _fieldGetterFactory.getter (obj, field);
474
535
}
475
536
}
@@ -485,28 +546,55 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
485
546
case _MODE_GETTER_ :
486
547
current = _getter (object);
487
548
break ;
549
+ case _MODE_GETTER_NOTIFIED_ :
550
+ _mode = _MODE_NOOP_ ; // no-op until next notification
551
+ current = _getter (object);
552
+ break ;
488
553
case _MODE_GETTER_OR_METHOD_CLOSURE_ :
489
- // NOTE: When Dart looks up a method "foo" on object "x", it returns a
490
- // new closure for each lookup. They compare equal via "==" but are no
491
- // identical(). There's no point getting a new value each time and
492
- // decide it's the same so we'll skip further checking after the first
493
- // time.
554
+ // NOTE: When Dart looks up a method "foo" on object "x", it returns a new closure for each
555
+ // lookup. They compare equal via "==" but are no identical(). There's no point getting a
556
+ // new value each time and decide it's the same so we'll skip further checking after the
557
+ // first time.
494
558
current = _getter (object);
495
559
if (current is Function && ! identical (current, _getter (object))) {
496
560
_mode = _MODE_NOOP_ ;
497
561
} else {
498
562
_mode = _MODE_GETTER_ ;
499
563
}
500
564
break ;
565
+ case _MODE_GETTER_OBS_OR_METHOD_CLOSURE_ :
566
+ current = _getter (object);
567
+ // no-op if closure (see _MODE_GETTER_OR_METHOD_CLOSURE_) or until the next notification
568
+ _mode = _MODE_NOOP_ ;
569
+ if (current is ! Function || identical (current, _getter (object))) {
570
+ var subscription = (object as obs.ChangeNotifier ).changes.listen ((records) {
571
+ for (var rec in records) {
572
+ // todo(vicb) Change to String if dartbug.com/17863 eventually gets fixed
573
+ if (smoke.symbolToName (rec.name) == field) {
574
+ // Run the dccd after the observed `object.field` is updated
575
+ _mode = _MODE_GETTER_NOTIFIED_ ;
576
+ break ;
577
+ }
578
+ }
579
+ });
580
+ _group._registerObservable (this , subscription);
581
+ }
582
+ break ;
501
583
case _MODE_MAP_FIELD_ :
502
584
current = object[field];
503
585
break ;
504
586
case _MODE_IDENTITY_ :
505
587
current = object;
506
588
_mode = _MODE_NOOP_ ;
507
589
break ;
590
+ case _MODE_MAP_NOTIFIED_ :
591
+ _mode = _MODE_NOOP_ ; // no-op until next notification
592
+ return (currentValue as _MapChangeRecord )._check (object);
508
593
case _MODE_MAP_ :
509
594
return (currentValue as _MapChangeRecord )._check (object);
595
+ case _MODE_LIST_NOTIFIED_ :
596
+ _mode = _MODE_NOOP_ ; // no-op until next notification
597
+ return (currentValue as _CollectionChangeRecord )._check (object);
510
598
case _MODE_ITERABLE_ :
511
599
return (currentValue as _CollectionChangeRecord )._check (object);
512
600
default :
@@ -515,8 +603,7 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
515
603
516
604
var last = currentValue;
517
605
if (! identical (last, current)) {
518
- if (last is String && current is String &&
519
- last == current) {
606
+ if (last is String && current is String && last == current) {
520
607
// This is false change in strings we need to recover, and pretend it
521
608
// is the same. We save the value so that next time identity will pass
522
609
currentValue = current;
@@ -531,7 +618,6 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
531
618
return false ;
532
619
}
533
620
534
-
535
621
void remove () {
536
622
_group._recordRemove (this );
537
623
}
@@ -540,8 +626,6 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
540
626
'${_mode < _MODE_NAMES .length ? _MODE_NAMES [_mode ] : '?' }[$field ]{$hashCode }' ;
541
627
}
542
628
543
- final Object _INITIAL_ = new Object ();
544
-
545
629
class _MapChangeRecord <K , V > implements MapChangeRecord <K , V > {
546
630
final _records = new HashMap <dynamic , KeyValueRecord >();
547
631
Map _map;
0 commit comments