Skip to content

Commit

Permalink
Refactor observable map. (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
matanlurey authored Nov 23, 2016
1 parent c9b6340 commit 2376aaf
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 254 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 0.19.0

* Refactor and deprecate `ObservableMap`-specific API
* `ObservableMap` no longer emits `#keys` and `#values` change records
* `ObservableMap.spy` is deprecated, becomes `.delegate` instead
* Potentially breaking: `ObservableMap` may no longer be extended

It is also considered deprecated to be notified of `length` changes.

## 0.18.1

* Bug fix: Do not throw when `Observable<T>.notifyChange` is used
Expand Down
3 changes: 1 addition & 2 deletions lib/observable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
library observable;

export 'src/change_notifier.dart' show ChangeNotifier, PropertyChangeNotifier;
export 'src/collections.dart' show ObservableList;
export 'src/collections.dart' show ObservableList, ObservableMap;
export 'src/differs.dart' show Differ, EqualityDiffer, ListDiffer, MapDiffer;
export 'src/records.dart'
show ChangeRecord, ListChangeRecord, MapChangeRecord, PropertyChangeRecord;
export 'src/observable.dart';
export 'src/observable_map.dart';
export 'src/to_observable.dart';
1 change: 1 addition & 0 deletions lib/src/collections.dart
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export 'collections/observable_list.dart';
export 'collections/observable_map.dart';
2 changes: 2 additions & 0 deletions lib/src/collections/observable_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import 'package:observable/src/differs.dart';
/// efficient to notify _when_ something has changed, instead of constantly
/// diffing lists to find a single change (like an inserted record). You may
/// accept an observable list to be notified of mutations:
/// ```
/// set names(List<String> names) {
/// clearAndWrite(names);
/// if (names is ObservableList<String>) {
/// names.listChanges.listen(smallIncrementalUpdate);
/// }
/// }
/// ```
///
/// *See [ListDiffer] to manually diff two lists instead*
abstract class ObservableList<E> implements List<E>, Observable {
Expand Down
181 changes: 181 additions & 0 deletions lib/src/collections/observable_map.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import 'dart:async';
import 'dart:collection';

import 'package:collection/collection.dart';
import 'package:observable/observable.dart';

/// A [Map] that broadcasts [changes] to subscribers for efficient mutations.
///
/// When client code expects a read heavy/write light workload, it is often more
/// efficient to notify _when_ something has changed, instead of constantly
/// diffing lists to find a single change (like an updated key-value). You may
/// accept an observable map to be notified of mutations:
/// ```
/// set grades(Map<String, int> grades) {
/// buildBook(grades);
/// if (names is ObservableMap<String>, int) {
/// grades.changes.listen(updateBook);
/// }
/// }
/// ```
///
/// *See [MapDiffer] to manually diff two lists instead*
abstract class ObservableMap<K, V> implements Map<K, V>, Observable {
/// Creates a new observable map.
factory ObservableMap() {
return new _ObservableDelegatingMap(new HashMap<K, V>());
}

/// Like [ObservableMap.from], but creates an empty type.
factory ObservableMap.createFromType(Map<K, V> other) {
ObservableMap<K, V> result;
if (other is LinkedHashMap) {
result = new ObservableMap<K, V>.linked();
} else if (other is SplayTreeMap) {
result = new ObservableMap<K, V>.sorted();
} else {
result = new ObservableMap<K, V>();
}
return result;
}

/// Create a new observable map using [map] as a backing store.
factory ObservableMap.delegate(Map<K, V> map) {
return new _ObservableDelegatingMap<K, V>(map);
}

/// Creates a new observable map that contains all entries in [other].
///
/// It will attempt to use the same backing map type if the other map is
/// either a [LinkedHashMap], [SplayTreeMap], or [HashMap]. Otherwise it will
/// fall back to using a [HashMap].
factory ObservableMap.from(Map<K, V> other) {
return new ObservableMap<K, V>.createFromType(other)..addAll(other);
}

/// Creates a new observable map using a [LinkedHashMap].
factory ObservableMap.linked() {
return new _ObservableDelegatingMap<K, V>(new LinkedHashMap<K, V>());
}

/// Creates a new observable map using a [SplayTreeMap].
factory ObservableMap.sorted() {
return new _ObservableDelegatingMap<K, V>(new SplayTreeMap<K, V>());
}

/// Creates a new observable map wrapping [other].
@Deprecated('Use ObservableMap.delegate for API consistency')
factory ObservableMap.spy(Map<K, V> other) = ObservableMap<K, V>.delegate;
}

class _ObservableDelegatingMap<K, V> extends DelegatingMap<K, V>
implements ObservableMap<K, V> {
final _allChanges = new ChangeNotifier();

_ObservableDelegatingMap(Map<K, V> map) : super(map);

// Observable

@override
Stream<List<ChangeRecord>> get changes => _allChanges.changes;

// ChangeNotifier (deprecated for ObservableMap)

@override
bool deliverChanges() => _allChanges.deliverChanges();

@override
bool get hasObservers => _allChanges.hasObservers;

@override
void notifyChange([ChangeRecord change]) {
_allChanges.notifyChange(change);
}

@override
/*=T*/ notifyPropertyChange/*<T>*/(
Symbol field,
/*=T*/
oldValue,
/*=T*/
newValue,
) {
if (oldValue != newValue) {
_allChanges.notifyChange(
new PropertyChangeRecord/*<T>*/(this, field, oldValue, newValue),
);
}
return newValue;
}

@override
void observed() {}

@override
void unobserved() {}

@override
operator []=(K key, V newValue) {
if (!hasObservers) {
super[key] = newValue;
return;
}

final oldLength = super.length;
V oldValue = super[key];
super[key] = newValue;

if (oldLength != length) {
notifyPropertyChange(#length, oldLength, length);
notifyChange(new MapChangeRecord<K, V>.insert(key, newValue));
} else {
notifyChange(new MapChangeRecord<K, V>(key, oldValue, newValue));
}
}

@override
void addAll(Map<K, V> other) {
if (!hasObservers) {
super.addAll(other);
return;
}

other.forEach((k, v) => this[k] = v);
}

@override
V putIfAbsent(K key, V ifAbsent()) {
final oldLength = length;
final result = super.putIfAbsent(key, ifAbsent);
if (hasObservers && oldLength != length) {
notifyPropertyChange(#length, oldLength, length);
notifyChange(new MapChangeRecord<K, V>.insert(key, result));
}
return result;
}

@override
V remove(Object key) {
final oldLength = length;
final result = super.remove(key);
if (hasObservers && oldLength != length) {
notifyChange(new MapChangeRecord<K, V>.remove(key as K, result));
notifyPropertyChange(#length, oldLength, length);
}
return result;
}

@override
void clear() {
if (!hasObservers || isEmpty) {
super.clear();
return;
}
final oldLength = length;
forEach((k, v) {
notifyChange(new MapChangeRecord<K, V>.remove(k, v));
});
notifyPropertyChange(#length, oldLength, 0);
super.clear();
}
}
167 changes: 0 additions & 167 deletions lib/src/observable_map.dart

This file was deleted.

Loading

0 comments on commit 2376aaf

Please sign in to comment.