| Index: pkg/observe/lib/src/observable.dart
|
| diff --git a/pkg/observe/lib/src/observable.dart b/pkg/observe/lib/src/observable.dart
|
| index f87de36062bc9b5a450389415cd5a25bdf93c730..ca9408589a2c4c655dd5c91641501965dc04d537 100644
|
| --- a/pkg/observe/lib/src/observable.dart
|
| +++ b/pkg/observe/lib/src/observable.dart
|
| @@ -5,87 +5,27 @@
|
| part of observe;
|
|
|
| /**
|
| - * Interface representing an observable object. This is used by data in
|
| - * model-view architectures to notify interested parties of [changes].
|
| + * Represents an object with observable properties. This is used by data in
|
| + * model-view architectures to notify interested parties of [changes] to the
|
| + * object's properties (fields or getter/setter pairs).
|
| *
|
| - * This object does not require any specific technique to implement
|
| - * observability. If you mixin [ObservableMixin], [dirtyCheck] will know to
|
| - * check for changes on the object. You may also implement change notification
|
| - * yourself, by calling [notifyChange].
|
| + * The interface does not require any specific technique to implement
|
| + * observability. You can implement it in the following ways:
|
| *
|
| - * You can use [ObservableBase] or [ObservableMixin] to implement this.
|
| + * - extend or mixin this class, and let the application call [dirtyCheck]
|
| + * periodically to check for changes to your object.
|
| + * - extend or mixin [ChangeNotifier], and implement change notifications
|
| + * manually by calling [notifyPropertyChange] from your setters.
|
| + * - implement this interface and provide your own implementation.
|
| */
|
| abstract class Observable {
|
| /**
|
| - * The stream of change records to this object. Records will be delivered
|
| - * asynchronously.
|
| - *
|
| - * [deliverChanges] can be called to force synchronous delivery.
|
| - */
|
| - Stream<List<ChangeRecord>> get changes;
|
| -
|
| - /**
|
| - * Synchronously deliver pending [changes]. Returns true if any records were
|
| - * delivered, otherwise false.
|
| - */
|
| - // TODO(jmesserly): this is a bit different from the ES Harmony version, which
|
| - // allows delivery of changes to a particular observer:
|
| - // http://wiki.ecmascript.org/doku.php?id=harmony:observe#object.deliverchangerecords
|
| - //
|
| - // The rationale for that, and for async delivery in general, is the principal
|
| - // that you shouldn't run code (observers) when it doesn't expect to be run.
|
| - // If you do that, you risk violating invariants that the code assumes.
|
| - //
|
| - // For this reason, we need to match the ES Harmony version. The way we can do
|
| - // this in Dart is to add a method on StreamSubscription (possibly by
|
| - // subclassing Stream* types) that immediately delivers records for only
|
| - // that subscription. Alternatively, we could consider using something other
|
| - // than Stream to deliver the multicast change records, and provide an
|
| - // Observable->Stream adapter.
|
| - //
|
| - // Also: we should be delivering changes to the observer (subscription) based
|
| - // on the birth order of the observer. This is for compatibility with ES
|
| - // Harmony as well as predictability for app developers.
|
| - bool deliverChanges();
|
| -
|
| - /**
|
| - * Notify observers of a change.
|
| - *
|
| - * For most objects [ObservableMixin.notifyPropertyChange] is more
|
| - * convenient, but collections sometimes deliver other types of changes such
|
| - * as a [ListChangeRecord].
|
| - */
|
| - void notifyChange(ChangeRecord record);
|
| -
|
| - /**
|
| - * True if this object has any observers, and should call
|
| - * [notifyChange] for changes.
|
| - */
|
| - bool get hasObservers;
|
| -
|
| - /**
|
| - * Performs dirty checking of objects that inherit from [ObservableMixin].
|
| + * Performs dirty checking of objects that inherit from [Observable].
|
| * This scans all observed objects using mirrors and determines if any fields
|
| * have changed. If they have, it delivers the changes for the object.
|
| */
|
| static void dirtyCheck() => dirtyCheckObservables();
|
| -}
|
|
|
| -/**
|
| - * Base class implementing [Observable].
|
| - *
|
| - * When a field, property, or indexable item is changed, the change record
|
| - * will be sent to [changes].
|
| - */
|
| -class ObservableBase = Object with ObservableMixin;
|
| -
|
| -/**
|
| - * Mixin for implementing [Observable] objects.
|
| - *
|
| - * When a field, property, or indexable item is changed, the change record
|
| - * will be sent to [changes].
|
| - */
|
| -abstract class ObservableMixin implements Observable {
|
| StreamController _changes;
|
| InstanceMirror _mirror;
|
|
|
| @@ -94,6 +34,12 @@ abstract class ObservableMixin implements Observable {
|
|
|
| static final _objectType = reflectClass(Object);
|
|
|
| + /**
|
| + * The stream of change records to this object. Records will be delivered
|
| + * asynchronously.
|
| + *
|
| + * [deliverChanges] can be called to force synchronous delivery.
|
| + */
|
| Stream<List<ChangeRecord>> get changes {
|
| if (_changes == null) {
|
| _changes = new StreamController.broadcast(sync: true,
|
| @@ -102,6 +48,10 @@ abstract class ObservableMixin implements Observable {
|
| return _changes.stream;
|
| }
|
|
|
| + /**
|
| + * True if this object has any observers, and should call
|
| + * [notifyChange] for changes.
|
| + */
|
| bool get hasObservers => _changes != null && _changes.hasListener;
|
|
|
| void _observed() {
|
| @@ -144,6 +94,28 @@ abstract class ObservableMixin implements Observable {
|
| }
|
| }
|
|
|
| + /**
|
| + * Synchronously deliver pending [changes]. Returns true if any records were
|
| + * delivered, otherwise false.
|
| + */
|
| + // TODO(jmesserly): this is a bit different from the ES Harmony version, which
|
| + // allows delivery of changes to a particular observer:
|
| + // http://wiki.ecmascript.org/doku.php?id=harmony:observe#object.deliverchangerecords
|
| + //
|
| + // The rationale for that, and for async delivery in general, is the principal
|
| + // that you shouldn't run code (observers) when it doesn't expect to be run.
|
| + // If you do that, you risk violating invariants that the code assumes.
|
| + //
|
| + // For this reason, we need to match the ES Harmony version. The way we can do
|
| + // this in Dart is to add a method on StreamSubscription (possibly by
|
| + // subclassing Stream* types) that immediately delivers records for only
|
| + // that subscription. Alternatively, we could consider using something other
|
| + // than Stream to deliver the multicast change records, and provide an
|
| + // Observable->Stream adapter.
|
| + //
|
| + // Also: we should be delivering changes to the observer (subscription) based
|
| + // on the birth order of the observer. This is for compatibility with ES
|
| + // Harmony as well as predictability for app developers.
|
| bool deliverChanges() {
|
| if (_values == null || !hasObservers) return false;
|
|
|
| @@ -154,9 +126,9 @@ abstract class ObservableMixin implements Observable {
|
|
|
| _values.forEach((name, oldValue) {
|
| var newValue = _mirror.getField(name).reflectee;
|
| - if (!identical(oldValue, newValue)) {
|
| + if (oldValue != newValue) {
|
| if (records == null) records = [];
|
| - records.add(new PropertyChangeRecord(name));
|
| + records.add(new PropertyChangeRecord(this, name, oldValue, newValue));
|
| _values[name] = newValue;
|
| }
|
| });
|
| @@ -171,7 +143,7 @@ abstract class ObservableMixin implements Observable {
|
| * Notify that the field [name] of this object has been changed.
|
| *
|
| * The [oldValue] and [newValue] are also recorded. If the two values are
|
| - * identical, no change will be recorded.
|
| + * equal, no change will be recorded.
|
| *
|
| * For convenience this returns [newValue].
|
| */
|
| @@ -179,9 +151,17 @@ abstract class ObservableMixin implements Observable {
|
| => _notifyPropertyChange(this, field, oldValue, newValue);
|
|
|
| /**
|
| - * Notify a change manually. This is *not* required for fields, but can be
|
| - * used for computed properties. *Note*: unlike [ChangeNotifierMixin] this
|
| - * will not schedule [deliverChanges]; use [Observable.dirtyCheck] instead.
|
| + * Notify observers of a change.
|
| + *
|
| + * For most objects [Observable.notifyPropertyChange] is more convenient, but
|
| + * collections sometimes deliver other types of changes such as a
|
| + * [ListChangeRecord].
|
| + *
|
| + * Notes:
|
| + * - This is *not* required for fields if you mixin or extend [Observable],
|
| + * but you can use it for computed properties.
|
| + * - Unlike [ChangeNotifier] this will not schedule [deliverChanges]; use
|
| + * [Observable.dirtyCheck] instead.
|
| */
|
| void notifyChange(ChangeRecord record) {
|
| if (!hasObservers) return;
|
| @@ -192,12 +172,18 @@ abstract class ObservableMixin implements Observable {
|
| }
|
|
|
| /**
|
| + * *Deprecated* use [Observable.notifyPropertyChange] instead.
|
| + *
|
| + * This API should not be used as it creates a
|
| + * [PropertyChangeRecord] without oldValue and newValue.
|
| + *
|
| * Notify the property change. Shorthand for:
|
| *
|
| - * target.notifyChange(new PropertyChangeRecord(targetName));
|
| + * target.notifyChange(new PropertyChangeRecord(target, name, null, null));
|
| */
|
| -void notifyProperty(Observable target, Symbol targetName) {
|
| - target.notifyChange(new PropertyChangeRecord(targetName));
|
| +@deprecated
|
| +void notifyProperty(Observable target, Symbol name) {
|
| + target.notifyChange(new PropertyChangeRecord(target, name, null, null));
|
| }
|
|
|
| // TODO(jmesserly): remove the instance method and make this top-level method
|
| @@ -205,10 +191,8 @@ void notifyProperty(Observable target, Symbol targetName) {
|
| _notifyPropertyChange(Observable obj, Symbol field, Object oldValue,
|
| Object newValue) {
|
|
|
| - // TODO(jmesserly): should this be == instead of identical, to prevent
|
| - // spurious loops?
|
| - if (obj.hasObservers && !identical(oldValue, newValue)) {
|
| - obj.notifyChange(new PropertyChangeRecord(field));
|
| + if (obj.hasObservers && oldValue != newValue) {
|
| + obj.notifyChange(new PropertyChangeRecord(obj, field, oldValue, newValue));
|
| }
|
| return newValue;
|
| }
|
|
|