Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(10)

Side by Side Diff: pkg/observe/lib/src/observable.dart

Issue 20631002: [pkg:observe] Add support for explicit change notification, even on dirty-checked objects (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: revert pubspec Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/observe/lib/src/change_notifier.dart ('k') | pkg/observe/test/observe_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, 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 part of observe; 5 part of observe;
6 6
7 /** 7 /**
8 * Use `@observable` to make a field automatically observable. 8 * Use `@observable` to make a field automatically observable.
9 */ 9 */
10 const Object observable = const _ObservableAnnotation(); 10 const Object observable = const _ObservableAnnotation();
11 11
12 /** 12 /**
13 * Interface representing an observable object. This is used by data in 13 * Interface representing an observable object. This is used by data in
14 * model-view architectures to notify interested parties of [changes]. 14 * model-view architectures to notify interested parties of [changes].
15 * 15 *
16 * This object does not require any specific technique to implement 16 * This object does not require any specific technique to implement
17 * observability. However if you implement change notification yourself, you 17 * observability. If you mixin [ObservableMixin], [dirtyCheck] will know to
18 * should also implement [ChangeNotifier], so [dirtyCheck] knows to skip the 18 * check for changes on the object. You may also implement change notification
19 * object. 19 * yourself, by calling [notifyChange].
20 * 20 *
21 * You can use [ObservableBase] or [ObservableMixin] to implement this. 21 * You can use [ObservableBase] or [ObservableMixin] to implement this.
22 */ 22 */
23 abstract class Observable { 23 abstract class Observable {
24 /** 24 /**
25 * The stream of change records to this object. Records will be delivered 25 * The stream of change records to this object. Records will be delivered
26 * asynchronously. 26 * asynchronously.
27 * 27 *
28 * [deliverChanges] can be called to force synchronous delivery. 28 * [deliverChanges] can be called to force synchronous delivery.
29 */ 29 */
(...skipping 10 matching lines...) Expand all
40 // The rationale for that, and for async delivery in general, is the principal 40 // The rationale for that, and for async delivery in general, is the principal
41 // that you shouldn't run code (observers) when it doesn't expect to be run. 41 // that you shouldn't run code (observers) when it doesn't expect to be run.
42 // If you do that, you risk violating invariants that the code assumes. 42 // If you do that, you risk violating invariants that the code assumes.
43 // 43 //
44 // For this reason, we need to match the ES Harmony version. The way we can do 44 // For this reason, we need to match the ES Harmony version. The way we can do
45 // this in Dart is to add a method on StreamSubscription (possibly by 45 // this in Dart is to add a method on StreamSubscription (possibly by
46 // subclassing Stream* types) that immediately delivers records for only 46 // subclassing Stream* types) that immediately delivers records for only
47 // that subscription. Alternatively, we could consider using something other 47 // that subscription. Alternatively, we could consider using something other
48 // than Stream to deliver the multicast change records, and provide an 48 // than Stream to deliver the multicast change records, and provide an
49 // Observable->Stream adapter. 49 // Observable->Stream adapter.
50 //
51 // Also: we should be delivering changes to the observer (subscription) based
52 // on the birth order of the observer. This is for compatibility with ES
53 // Harmony as well as predictability for app developers.
50 bool deliverChanges(); 54 bool deliverChanges();
51 55
52 /** 56 /**
57 * Notify observers of a change.
58 *
59 * For most objects [ObservableMixin.notifyPropertyChange] is more
60 * convenient, but collections sometimes deliver other types of changes such
61 * as a [ListChangeRecord].
62 */
63 void notifyChange(ChangeRecord record);
64
65 /**
53 * Performs dirty checking of objects that inherit from [ObservableMixin]. 66 * Performs dirty checking of objects that inherit from [ObservableMixin].
54 * This scans all observed objects using mirrors and determines if any fields 67 * This scans all observed objects using mirrors and determines if any fields
55 * have changed. If they have, it delivers the changes for the object. 68 * have changed. If they have, it delivers the changes for the object.
56 */ 69 */
57 static void dirtyCheck() => dirtyCheckObservables(); 70 static void dirtyCheck() => dirtyCheckObservables();
58 } 71 }
59 72
60 /** 73 /**
61 * Base class implementing [Observable]. 74 * Base class implementing [Observable].
62 * 75 *
63 * When a field, property, or indexable item is changed, the change record 76 * When a field, property, or indexable item is changed, the change record
64 * will be sent to [changes]. 77 * will be sent to [changes].
65 */ 78 */
66 typedef ObservableBase = Object with ObservableMixin; 79 typedef ObservableBase = Object with ObservableMixin;
67 80
68 /** 81 /**
69 * Mixin for implementing [Observable] objects. 82 * Mixin for implementing [Observable] objects.
70 * 83 *
71 * When a field, property, or indexable item is changed, the change record 84 * When a field, property, or indexable item is changed, the change record
72 * will be sent to [changes]. 85 * will be sent to [changes].
73 */ 86 */
74 abstract class ObservableMixin implements Observable { 87 abstract class ObservableMixin implements Observable {
75 StreamController _changes; 88 StreamController _changes;
76 InstanceMirror _mirror; 89 InstanceMirror _mirror;
77 90
78 Map<Symbol, Object> _values; 91 Map<Symbol, Object> _values;
92 List<ChangeRecord> _records;
79 93
80 Stream<List<ChangeRecord>> get changes { 94 Stream<List<ChangeRecord>> get changes {
81 if (_changes == null) { 95 if (_changes == null) {
82 _changes = new StreamController.broadcast(sync: true, 96 _changes = new StreamController.broadcast(sync: true,
83 onListen: _observed, onCancel: _unobserved); 97 onListen: _observed, onCancel: _unobserved);
84 } 98 }
85 return _changes.stream; 99 return _changes.stream;
86 } 100 }
87 101
88 /** 102 /**
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
125 // This will happen automatically at the next call to dirtyCheck. 139 // This will happen automatically at the next call to dirtyCheck.
126 if (_values != null) { 140 if (_values != null) {
127 _mirror = null; 141 _mirror = null;
128 _values = null; 142 _values = null;
129 } 143 }
130 } 144 }
131 145
132 bool deliverChanges() { 146 bool deliverChanges() {
133 if (_values == null || !hasObservers) return false; 147 if (_values == null || !hasObservers) return false;
134 148
135 List changes = null; 149 // Start with an manually notified records (computed properties, etc),
Siggi Cherem (dart-lang) 2013/07/26 15:53:14 an manually => a list of manually?
Jennifer Messerly 2013/07/26 18:14:43 fixed. not sure what "an" was doing there.
150 // then scan all fields for additional changes.
151 List records = _records;
152 _records = null;
153
136 _values.forEach((name, oldValue) { 154 _values.forEach((name, oldValue) {
137 var newValue = _mirror.getField(name).reflectee; 155 var newValue = _mirror.getField(name).reflectee;
138 if (!identical(oldValue, newValue)) { 156 if (!identical(oldValue, newValue)) {
139 if (changes == null) changes = <PropertyChangeRecord>[]; 157 if (records == null) records = [];
140 changes.add(new PropertyChangeRecord(name)); 158 records.add(new PropertyChangeRecord(name));
141 _values[name] = newValue; 159 _values[name] = newValue;
142 } 160 }
143 }); 161 });
144 162
145 if (changes == null) return false; 163 if (records == null) return false;
146 164
147 // TODO(jmesserly): make "changes" immutable 165 _changes.add(new UnmodifiableListView<ChangeRecord>(records));
148 _changes.add(changes);
149 return true; 166 return true;
150 } 167 }
168
169 /**
170 * Notify that the field [name] of this object has been changed.
171 *
172 * The [oldValue] and [newValue] are also recorded. If the two values are
173 * identical, no change will be recorded.
174 *
175 * For convenience this returns [newValue].
176 */
177 notifyPropertyChange(Symbol field, Object oldValue, Object newValue)
178 => _notifyPropertyChange(this, field, oldValue, newValue);
179
180 /**
181 * Notify a change manually. This is *not* required for fields, but can be
182 * used for computed properties. *Note*: unlike [ChangeNotifierMixin] this
183 * will not schedule [deliverChanges]; use [Observable.dirtyCheck] instead.
184 */
185 void notifyChange(ChangeRecord record) {
186 if (!hasObservers) return;
187
188 if (_records == null) _records = [];
189 _records.add(record);
190 }
151 } 191 }
152 192
193 // TODO(jmesserly): remove the instance method and make this top-level method
194 // public instead?
195 _notifyPropertyChange(Observable obj, Symbol field, Object oldValue,
196 Object newValue) {
197
198 // TODO(jmesserly): should this be == instead of identical, to prevent
199 // spurious loops?
200 if (obj.hasObservers && !identical(oldValue, newValue)) {
201 obj.notifyChange(new PropertyChangeRecord(field));
202 }
203 return newValue;
204 }
205
206
153 /** 207 /**
154 * The type of the `@observable` annotation. 208 * The type of the `@observable` annotation.
155 * 209 *
156 * Library private because you should be able to use the [observable] field 210 * Library private because you should be able to use the [observable] field
157 * to get the one and only instance. We could make it public though, if anyone 211 * to get the one and only instance. We could make it public though, if anyone
158 * needs it for some reason. 212 * needs it for some reason.
159 */ 213 */
160 class _ObservableAnnotation { 214 class _ObservableAnnotation {
161 const _ObservableAnnotation(); 215 const _ObservableAnnotation();
162 } 216 }
OLDNEW
« no previous file with comments | « pkg/observe/lib/src/change_notifier.dart ('k') | pkg/observe/test/observe_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698