Add unmodifiable versions of each observable collection (#21)
* Add an ObservableSet implementation
* Add tests, cleanup.
* Cleanup licensing.
* Add unmodifiable observable collections
* Change versions.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc5e443..99089b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 0.20.1
+
+* Add `Observable<List|Set|Map>.unmodifiable` for immutable collections
+* Add `Observable<List|Set|Map>.EMPTY` for empty immutable collections
+ * This can be used as an optimization for libraries that always
+ need to return an observable collection, but don't want to
+ allocate a new instance to represent an empty immutable.
+
## 0.20.0
* Add `ObservableSet`, `SetChangeRecord`, and `SetDiffer`
diff --git a/lib/src/collections/observable_list.dart b/lib/src/collections/observable_list.dart
index c8266c7..9b86089 100644
--- a/lib/src/collections/observable_list.dart
+++ b/lib/src/collections/observable_list.dart
@@ -25,6 +25,11 @@
///
/// *See [ListDiffer] to manually diff two lists instead*
abstract class ObservableList<E> implements List<E>, Observable {
+ /// An empty observable list that never has changes.
+ static const ObservableList EMPTY = const _ObservableUnmodifiableList(
+ const [],
+ );
+
/// Applies [changes] to [previous] based on the [current] values.
///
/// ## Deprecated
@@ -83,6 +88,20 @@
return new ObservableList<E>(length);
}
+ /// Create new unmodifiable list from [list].
+ ///
+ /// [ObservableList.changes] and [ObservableList.listChanges] both always
+ /// return an empty stream, and mutating or adding change records throws an
+ /// [UnsupportedError].
+ factory ObservableList.unmodifiable(
+ List<E> list,
+ ) {
+ if (list is! UnmodifiableListView<E>) {
+ list = new List<E>.unmodifiable(list);
+ }
+ return new _ObservableUnmodifiableList<E>(list);
+ }
+
@Deprecated('No longer supported. Just use deliverChanges')
bool deliverListChanges();
@@ -442,3 +461,60 @@
}
}
}
+
+class _ObservableUnmodifiableList<E> extends DelegatingList<E>
+ implements ObservableList<E> {
+ const _ObservableUnmodifiableList(List<E> list) : super(list);
+
+ @override
+ Stream<List<ChangeRecord>> get changes => const Stream.empty();
+
+ @override
+ bool deliverChanges() => false;
+
+ @override
+ bool deliverListChanges() => false;
+
+ @override
+ void discardListChanges() {}
+
+ @override
+ final bool hasListObservers = false;
+
+ @override
+ final bool hasObservers = false;
+
+ @override
+ Stream<List<ListChangeRecord<E>>> get listChanges => const Stream.empty();
+
+ @override
+ void notifyChange([ChangeRecord change]) {
+ throw new UnsupportedError('Not modifiable');
+ }
+
+ @override
+ void notifyListChange(
+ int index, {
+ List<E> removed: const [],
+ int addedCount: 0,
+ }) {
+ throw new UnsupportedError('Not modifiable');
+ }
+
+ @override
+ /*=T*/ notifyPropertyChange/*<T>*/(
+ Symbol field,
+ /*=T*/
+ oldValue,
+ /*=T*/
+ newValue,
+ ) {
+ throw new UnsupportedError('Not modifiable');
+ }
+
+ @override
+ void observed() {}
+
+ @override
+ void unobserved() {}
+}
diff --git a/lib/src/collections/observable_map.dart b/lib/src/collections/observable_map.dart
index 3c2ed48..e83664d 100644
--- a/lib/src/collections/observable_map.dart
+++ b/lib/src/collections/observable_map.dart
@@ -25,6 +25,9 @@
///
/// *See [MapDiffer] to manually diff two lists instead*
abstract class ObservableMap<K, V> implements Map<K, V>, Observable {
+ /// An empty observable map that never has changes.
+ static const ObservableMap EMPTY = const _ObservableUnmodifiableMap(const {});
+
/// Creates a new observable map.
factory ObservableMap() {
return new _ObservableDelegatingMap(new HashMap<K, V>());
@@ -70,6 +73,17 @@
/// 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;
+
+ /// Create a new unmodifiable map from [map].
+ ///
+ /// [ObservableMap.changes] always returns an empty stream, and mutating or
+ /// adding change records throws an [UnsupportedError].
+ factory ObservableMap.unmodifiable(Map<K, V> map) {
+ if (map is! UnmodifiableMapView<K, V>) {
+ map = new Map<K, V>.unmodifiable(map);
+ }
+ return new _ObservableUnmodifiableMap(map);
+ }
}
class _ObservableDelegatingMap<K, V> extends DelegatingMap<K, V>
@@ -183,3 +197,40 @@
super.clear();
}
}
+
+class _ObservableUnmodifiableMap<K, V> extends DelegatingMap<K, V>
+ implements ObservableMap<K, V> {
+ const _ObservableUnmodifiableMap(Map<K, V> map) : super(map);
+
+ @override
+ Stream<List<ChangeRecord>> get changes => const Stream.empty();
+
+ @override
+ bool deliverChanges() => false;
+
+ // TODO: implement hasObservers
+ @override
+ final bool hasObservers = false;
+
+ @override
+ void notifyChange([ChangeRecord change]) {
+ throw new UnsupportedError('Not modifiable');
+ }
+
+ @override
+ /*=T*/ notifyPropertyChange/*<T>*/(
+ Symbol field,
+ /*=T*/
+ oldValue,
+ /*=T*/
+ newValue,
+ ) {
+ throw new UnsupportedError('Not modifiable');
+ }
+
+ @override
+ void observed() {}
+
+ @override
+ void unobserved() {}
+}
diff --git a/lib/src/collections/observable_set.dart b/lib/src/collections/observable_set.dart
index 762a782..b8e347f 100644
--- a/lib/src/collections/observable_set.dart
+++ b/lib/src/collections/observable_set.dart
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+import 'dart:async';
import 'dart:collection';
import 'package:collection/collection.dart';
@@ -25,6 +26,11 @@
/// *See [SetDiffer] to manually diff two lists instead*
abstract class ObservableSet<E>
implements Observable<SetChangeRecord<E>>, Set<E> {
+ /// An empty observable set that never has changes.
+ static const ObservableSet EMPTY = const _UnmodifiableObservableSet(
+ const _UnmodifiableEmptySet(),
+ );
+
/// Create a new empty observable set.
factory ObservableSet() => new _DelegatingObservableSet<E>(new HashSet<E>());
@@ -62,6 +68,17 @@
factory ObservableSet.sorted() {
return new _DelegatingObservableSet<E>(new SplayTreeSet<E>());
}
+
+ /// Create a new unmodifiable set from [set].
+ ///
+ /// [ObservableSet.changes] always returns an empty stream, and mutating or
+ /// adding change records throws an [UnsupportedError].
+ factory ObservableSet.unmodifiable(Set<E> set) {
+ if (set is! UnmodifiableSetView<E>) {
+ set = new UnmodifiableSetView<E>(set);
+ }
+ return new _UnmodifiableObservableSet(set);
+ }
}
class _DelegatingObservableSet<E> extends DelegatingSet<E>
@@ -116,3 +133,86 @@
removeWhere((e) => !test(e));
}
}
+
+class _UnmodifiableEmptySet<E> extends IterableBase<E> implements Set<E> {
+ const _UnmodifiableEmptySet();
+
+ @override
+ bool add(E value) => false;
+
+ @override
+ void addAll(Iterable<E> elements) {}
+
+ @override
+ void clear() {}
+
+ @override
+ bool containsAll(Iterable<Object> other) => other.isEmpty;
+
+ @override
+ Set<E> difference(Set<Object> other) => other.toSet();
+
+ @override
+ Set<E> intersection(Set<Object> other) => this;
+
+ @override
+ Iterator<E> get iterator => const <E>[].iterator;
+
+ @override
+ E lookup(Object object) => null;
+
+ @override
+ bool remove(Object value) => false;
+
+ @override
+ void removeAll(Iterable<Object> elements) {}
+
+ @override
+ void removeWhere(bool test(E element)) {}
+
+ @override
+ void retainAll(Iterable<Object> elements) {}
+
+ @override
+ void retainWhere(bool test(E element)) {}
+
+ @override
+ Set<E> union(Set<E> other) => other.toSet();
+}
+
+class _UnmodifiableObservableSet<E> extends DelegatingSet<E>
+ implements ObservableSet<E> {
+ const _UnmodifiableObservableSet(Set<E> set) : super(set);
+
+ @override
+ Stream<List<SetChangeRecord<E>>> get changes => const Stream.empty();
+
+ @override
+ bool deliverChanges() => false;
+
+ // TODO: implement hasObservers
+ @override
+ final bool hasObservers = false;
+
+ @override
+ void notifyChange([ChangeRecord change]) {
+ throw new UnsupportedError('Not modifiable');
+ }
+
+ @override
+ /*=T*/ notifyPropertyChange/*<T>*/(
+ Symbol field,
+ /*=T*/
+ oldValue,
+ /*=T*/
+ newValue,
+ ) {
+ throw new UnsupportedError('Not modifiable');
+ }
+
+ @override
+ void observed() {}
+
+ @override
+ void unobserved() {}
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 84d9382..35b5a79 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: observable
-version: 0.20.0
+version: 0.20.1
author: Dart Team <misc@dartlang.org>
description: Support for marking objects as observable
homepage: https://github.com/dart-lang/observable