Skip to content

Commit d296efa

Browse files
authored
Deprecate Observable, add ChangeNotifier, setup travis (#11)
* Deprecate Observable, setup Travis * Make changes suggested via review * Update .travis.yml Remove stable branch, as `collection` dependency won't resolve on it.
1 parent 2446d7b commit d296efa

15 files changed

+327
-108
lines changed

.travis.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
language: dart
2+
3+
dart:
4+
- dev
5+
# Currently requires a dev-branch in order to use properly.
6+
# - stable
7+
8+
script: ./tool/presubmit.sh

CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,36 @@
1+
## 0.17.0
2+
3+
This is a larger change with a goal of no runtime changes for current
4+
customers, but in the future `Observable` will [become][issue_10] a very
5+
lightweight interface, i.e.:
6+
7+
```dart
8+
abstract class Observable<C extends ChangeRecord> {
9+
Stream<List<C>> get changes;
10+
}
11+
```
12+
13+
[issue_10]: https://github.com/dart-lang/observable/issues/10
14+
15+
* Started deprecating the wide `Observable` interface
16+
* `ChangeNotifier` should be used as a base class for these methods:
17+
* `Observable.observed`
18+
* `Observable.unobserved`
19+
* `Observable.hasObservers`
20+
* `Observable.deliverChanges`
21+
* `Observable.notifyChange`
22+
* `PropertyChangeNotifier` should be used for these methods:
23+
* `Observable.notifyPropertyChange`
24+
* Temporarily, `Observable` _uses_ `ChangeNotifier`
25+
* Existing users of anything but `implements Observable` should
26+
move to implementing or extending `ChangeNotifier`. In a
27+
future release `Observable` will reduce API surface down to
28+
an abstract `Stream<List<C>> get changes`.
29+
* Added the `ChangeNotifier` and `PropertyChangeNotifier` classes
30+
* Can be used to implement `Observable` in a generic manner
31+
* Observable is now `Observable<C extends ChangeRecord>`
32+
* When passing a generic type `C`, `notifyPropertyChange` is illegal
33+
134
## 0.16.0
235

336
* Refactored `MapChangeRecord`

lib/observable.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
library observable;
66

7+
export 'src/change_notifier.dart' show ChangeNotifier, PropertyChangeNotifier;
78
export 'src/differs.dart' show Differ, EqualityDiffer, ListDiffer, MapDiffer;
8-
export 'src/records.dart' show ChangeRecord, ListChangeRecord, MapChangeRecord;
9+
export 'src/records.dart'
10+
show ChangeRecord, ListChangeRecord, MapChangeRecord, PropertyChangeRecord;
911
export 'src/observable.dart';
1012
export 'src/observable_list.dart';
1113
export 'src/observable_map.dart';
12-
export 'src/property_change_record.dart';
1314
export 'src/to_observable.dart';

lib/src/change_notifier.dart

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import 'dart:async';
2+
3+
import 'package:meta/meta.dart';
4+
5+
import 'internal.dart';
6+
import 'observable.dart';
7+
import 'records.dart';
8+
9+
/// Supplies [changes] and various hooks to implement [Observable].
10+
///
11+
/// May use [notifyChange] to queue a change record; they are asynchronously
12+
/// delivered at the end of the VM turn.
13+
///
14+
/// [ChangeNotifier] may be extended, mixed in, or used as a delegate.
15+
class ChangeNotifier<C extends ChangeRecord> implements Observable<C> {
16+
StreamController<List<C>> _changes;
17+
18+
bool _scheduled = false;
19+
List<C> _queue;
20+
21+
/// Emits a list of changes when the state of the object changes.
22+
///
23+
/// Changes should produced in order, if significant.
24+
@override
25+
Stream<List<C>> get changes {
26+
return (_changes ??= new StreamController<List<C>>.broadcast(
27+
sync: true,
28+
onListen: observed,
29+
onCancel: unobserved,
30+
))
31+
.stream;
32+
}
33+
34+
/// May override to be notified when [changes] is first observed.
35+
@override
36+
@protected
37+
@mustCallSuper
38+
void observed() {}
39+
40+
/// May override to be notified when [changes] is no longer observed.
41+
@override
42+
@protected
43+
@mustCallSuper
44+
void unobserved() {
45+
_changes = _queue = null;
46+
}
47+
48+
/// If [hasObservers], synchronously emits [changes] that have been queued.
49+
///
50+
/// Returns `true` if changes were emitted.
51+
@override
52+
@protected
53+
@mustCallSuper
54+
bool deliverChanges() {
55+
List<ChangeRecord> changes;
56+
if (_scheduled && hasObservers) {
57+
if (_queue != null) {
58+
changes = freezeInDevMode(_queue);
59+
_queue = null;
60+
} else {
61+
changes = ChangeRecord.ANY;
62+
}
63+
_scheduled = false;
64+
_changes.add(changes);
65+
}
66+
return changes != null;
67+
}
68+
69+
/// Whether [changes] has at least one active listener.
70+
///
71+
/// May be used to optimize whether to produce change records.
72+
@override
73+
bool get hasObservers => _changes?.hasListener == true;
74+
75+
/// Schedules [change] to be delivered.
76+
///
77+
/// If [change] is omitted then [ChangeRecord.ANY] will be sent.
78+
///
79+
/// If there are no listeners to [changes], this method does nothing.
80+
@override
81+
void notifyChange([C change]) {
82+
if (!hasObservers) {
83+
return;
84+
}
85+
if (change != null) {
86+
(_queue ??= <C>[]).add(change);
87+
}
88+
if (!_scheduled) {
89+
scheduleMicrotask(deliverChanges);
90+
_scheduled = true;
91+
}
92+
}
93+
94+
@Deprecated('Exists to make migrations off Observable easier')
95+
@override
96+
@protected
97+
/*=T*/ notifyPropertyChange/*<T>*/(
98+
Symbol field,
99+
/*=T*/
100+
oldValue,
101+
/*=T*/
102+
newValue,
103+
) {
104+
throw new UnsupportedError('Not supported by ChangeNotifier');
105+
}
106+
}
107+
108+
/// Implements [notifyPropertyChange] for classes that need support.
109+
///
110+
/// Will be folded entirely into [PropertyChangeNotifier] in the future.
111+
@Deprecated('Exists to make migrations off Observable easier')
112+
abstract class PropertyChangeMixin implements ChangeNotifier {
113+
@override
114+
/*=T*/ notifyPropertyChange/*<T>*/(
115+
Symbol field,
116+
/*=T*/
117+
oldValue,
118+
/*=T*/
119+
newValue,
120+
) {
121+
if (hasObservers && oldValue != newValue) {
122+
notifyChange(
123+
new PropertyChangeRecord/*<T>*/(
124+
this,
125+
field,
126+
oldValue,
127+
newValue,
128+
),
129+
);
130+
}
131+
return newValue;
132+
}
133+
}
134+
135+
/// Supplies property `changes` and various hooks to implement [Observable].
136+
///
137+
/// May use `notifyChange` or `notifyPropertyChange` to queue a property change
138+
/// record; they are asynchronously delivered at the end of the VM turn.
139+
///
140+
/// [PropertyChangeNotifier] may be extended or used as a delegate. To use as
141+
/// a mixin, instead use with [PropertyChangeMixin]:
142+
/// with ChangeNotifier<PropertyChangeRecord>, PropertyChangeMixin
143+
class PropertyChangeNotifier = ChangeNotifier<PropertyChangeRecord>
144+
with PropertyChangeMixin;

lib/src/differs.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ part 'differs/map_differ.dart';
1717

1818
/// Generic comparisons between two comparable objects.
1919
abstract class Differ<E> {
20-
/// Returns a list of change records between [e1] and [e2].
20+
/// Returns a list of change records between [oldValue] and [newValue].
2121
///
2222
/// A return value of an empty [ChangeRecord.NONE] means no changes found.
2323
List<ChangeRecord> diff(E oldValue, E newValue);

lib/src/differs/list_differ.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ List<ListChangeRecord/*<E>*/ > _calcSplices/*<E>*/(
210210
}
211211

212212
if (currentStart == currentEnd) {
213-
final spliceRemoved = old.sublist(oldStart, oldStart + (oldEnd - oldStart));
213+
final spliceRemoved = old.sublist(oldStart, oldEnd);
214214
return [
215215
new ListChangeRecord/*<E>*/ .remove(
216216
current,

lib/src/observable.dart

Lines changed: 67 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,102 +5,103 @@
55
library observable.src.observable;
66

77
import 'dart:async';
8-
import 'dart:collection' show UnmodifiableListView;
98

109
import 'package:meta/meta.dart';
1110

12-
import 'property_change_record.dart' show PropertyChangeRecord;
13-
import 'records.dart' show ChangeRecord;
11+
import 'change_notifier.dart';
12+
import 'records.dart';
1413

15-
/// Represents an object with observable properties. This is used by data in
16-
/// model-view architectures to notify interested parties of [changes] to the
17-
/// object's properties (fields or getter/setter pairs).
14+
/// Represents an object with observable state or properties.
1815
///
1916
/// The interface does not require any specific technique to implement
20-
/// observability. You can implement it in the following ways:
21-
///
22-
/// - Deriving from this class via a mixin or base class. When a field,
23-
/// property, or indexable item is changed, the derived class should call
24-
/// [notifyPropertyChange]. See that method for an example.
25-
/// - Implementing this interface and providing your own implementation.
26-
abstract class Observable {
27-
StreamController<List<ChangeRecord>> _changes;
17+
/// observability. You may implement it in the following ways:
18+
/// - Extend or mixin [ChangeNotifier]
19+
/// - Implement the interface yourself and provide your own implementation
20+
abstract class Observable<C extends ChangeRecord> {
21+
// To be removed when https://github.com/dart-lang/observable/issues/10
22+
final ChangeNotifier<C> _delegate = new ChangeNotifier<C>();
2823

29-
List<ChangeRecord> _records;
24+
// Whether Observable was not given a type.
25+
final bool _isNotGeneric = C == dynamic;
3026

31-
/// The stream of property change records to this object, delivered
32-
/// asynchronously.
27+
/// Emits a list of changes when the state of the object changes.
3328
///
34-
/// [deliverChanges] can be called to force synchronous delivery.
35-
Stream<List<ChangeRecord>> get changes {
36-
if (_changes == null) {
37-
_changes = new StreamController.broadcast(
38-
sync: true, onListen: observed, onCancel: unobserved);
39-
}
40-
return _changes.stream;
41-
}
29+
/// Changes should produced in order, if significant.
30+
Stream<List<C>> get changes => _delegate.changes;
4231

43-
/// Derived classes may override this method to be called when the [changes]
44-
/// are first observed.
45-
// TODO(tvolkert): @mustCallSuper (github.com/dart-lang/sdk/issues/27275)
32+
/// May override to be notified when [changes] is first observed.
4633
@protected
47-
void observed() {}
34+
@mustCallSuper
35+
@Deprecated('Use ChangeNotifier instead to have this method available')
36+
// REMOVE IGNORE when https://github.com/dart-lang/observable/issues/10
37+
// ignore: invalid_use_of_protected_member
38+
void observed() => _delegate.observed();
4839

49-
/// Derived classes may override this method to be called when the [changes]
50-
/// are no longer being observed.
51-
// TODO(tvolkert): @mustCallSuper (github.com/dart-lang/sdk/issues/27275)
40+
/// May override to be notified when [changes] is no longer observed.
5241
@protected
53-
void unobserved() {
54-
// Free some memory
55-
_changes = null;
56-
}
42+
@mustCallSuper
43+
@Deprecated('Use ChangeNotifier instead to have this method available')
44+
// REMOVE IGNORE when https://github.com/dart-lang/observable/issues/10
45+
// ignore: invalid_use_of_protected_member
46+
void unobserved() => _delegate.unobserved();
5747

5848
/// True if this object has any observers.
59-
bool get hasObservers => _changes != null && _changes.hasListener;
49+
@Deprecated('Use ChangeNotifier instead to have this method available')
50+
bool get hasObservers => _delegate.hasObservers;
6051

61-
/// Synchronously deliver pending [changes].
52+
/// If [hasObservers], synchronously emits [changes] that have been queued.
6253
///
63-
/// Returns `true` if any records were delivered, otherwise `false`.
64-
/// Pending records will be cleared regardless, to keep newly added
65-
/// observers from being notified of changes that occurred before
66-
/// they started observing.
67-
bool deliverChanges() {
68-
List<ChangeRecord> records = _records;
69-
_records = null;
70-
if (hasObservers && records != null) {
71-
_changes.add(new UnmodifiableListView<ChangeRecord>(records));
72-
return true;
73-
}
74-
return false;
75-
}
54+
/// Returns `true` if changes were emitted.
55+
@Deprecated('Use ChangeNotifier instead to have this method available')
56+
// REMOVE IGNORE when https://github.com/dart-lang/observable/issues/10
57+
// ignore: invalid_use_of_protected_member
58+
bool deliverChanges() => _delegate.deliverChanges();
7659

7760
/// Notify that the [field] name of this object has been changed.
7861
///
7962
/// The [oldValue] and [newValue] are also recorded. If the two values are
8063
/// equal, no change will be recorded.
8164
///
8265
/// For convenience this returns [newValue].
66+
///
67+
/// ## Deprecated
68+
///
69+
/// All [Observable] objects will no longer be required to emit change records
70+
/// when any property changes. For example, `ObservableList` will only emit
71+
/// on `ObservableList.changes`, instead of on `ObservableList.listChanges`.
72+
///
73+
/// If you are using a typed `implements/extends Observable<C>`, it is illegal
74+
/// to call this method - will throw an [UnsupportedError] when called.
75+
@Deprecated('Use PropertyChangeNotifier')
8376
/*=T*/ notifyPropertyChange/*<T>*/(
84-
Symbol field, /*=T*/ oldValue, /*=T*/ newValue) {
77+
Symbol field,
78+
/*=T*/
79+
oldValue,
80+
/*=T*/
81+
newValue,
82+
) {
8583
if (hasObservers && oldValue != newValue) {
86-
notifyChange(new PropertyChangeRecord(this, field, oldValue, newValue));
84+
if (_isNotGeneric) {
85+
notifyChange(
86+
new PropertyChangeRecord(
87+
this,
88+
field,
89+
oldValue,
90+
newValue,
91+
) as C,
92+
);
93+
} else {
94+
throw new UnsupportedError('Generic typed Observable does not support');
95+
}
8796
}
8897
return newValue;
8998
}
9099

91-
/// Notify observers of a change.
100+
/// Schedules [change] to be delivered.
92101
///
93-
/// This will automatically schedule [deliverChanges].
102+
/// If [change] is omitted then [ChangeRecord.ANY] will be sent.
94103
///
95-
/// For most objects [Observable.notifyPropertyChange] is more convenient, but
96-
/// collections sometimes deliver other types of changes such as a
97-
/// [MapChangeRecord].
98-
void notifyChange(ChangeRecord record) {
99-
if (!hasObservers) return;
100-
if (_records == null) {
101-
_records = [];
102-
scheduleMicrotask(deliverChanges);
103-
}
104-
_records.add(record);
105-
}
104+
/// If there are no listeners to [changes], this method does nothing.
105+
@Deprecated('Use ChangeNotifier instead to have this method available')
106+
void notifyChange([C change]) => _delegate.notifyChange(change);
106107
}

0 commit comments

Comments
 (0)