Skip to content

Commit 851e078

Browse files
committed
Allow a custom equals parameter for observable collections
1 parent d14a27e commit 851e078

File tree

7 files changed

+147
-53
lines changed

7 files changed

+147
-53
lines changed

mobx/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.2.4
2+
3+
- Allow a custom equals parameter for observable collections( ObservableList, ObservableMap, ObservableSet ) - [@amondnet](https://github.com/amondnet)
4+
15
## 2.2.3
26

37
- Avoid unnecessary observable notifications of `@observable` `Iterable` or `Map` fields of Stores by [@amondnet](https://github.com/amondnet) in [#951](https://github.com/mobxjs/mobx.dart/pull/951)

mobx/lib/src/api/observable_collections.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import 'dart:collection';
22
import 'dart:math';
33

4+
import 'package:collection/collection.dart';
45
import 'package:meta/meta.dart';
56
import 'package:mobx/mobx.dart';
7+
import 'package:mobx/src/utils.dart';
68

79
part 'observable_collections/observable_list.dart';
810
part 'observable_collections/observable_map.dart';

mobx/lib/src/api/observable_collections/observable_list.dart

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,23 @@ class ObservableList<T>
2525
ListMixin<T>
2626
implements
2727
Listenable<ListChange<T>> {
28-
ObservableList({ReactiveContext? context, String? name})
29-
: this._wrap(context, _observableListAtom<T>(context, name), []);
28+
ObservableList(
29+
{ReactiveContext? context, String? name, EqualityComparer<T>? equals})
30+
: this._wrap(context, _observableListAtom<T>(context, name), [], equals);
3031

3132
ObservableList.of(Iterable<T> elements,
32-
{ReactiveContext? context, String? name})
33+
{ReactiveContext? context, String? name, EqualityComparer<T>? equals})
3334
: this._wrap(context, _observableListAtom<T>(context, name),
34-
List<T>.of(elements, growable: true));
35+
List<T>.of(elements, growable: true), equals);
3536

36-
ObservableList._wrap(ReactiveContext? context, this._atom, this._list)
37+
ObservableList._wrap(
38+
ReactiveContext? context, this._atom, this._list, this._equals)
3739
: _context = context ?? mainContext;
3840

3941
final ReactiveContext _context;
4042
final Atom _atom;
4143
final List<T> _list;
44+
final EqualityComparer<T>? _equals;
4245

4346
List<T> get nonObservableInner => _list;
4447

@@ -96,7 +99,7 @@ class ObservableList<T>
9699
_context.conditionallyRunInAction(() {
97100
final oldValue = _list[index];
98101

99-
if (oldValue != value) {
102+
if (_areEquals(oldValue, value)) {
100103
_list[index] = value;
101104
_notifyElementUpdate(index, value, oldValue);
102105
}
@@ -167,10 +170,18 @@ class ObservableList<T>
167170
}
168171

169172
@override
170-
Map<int, T> asMap() => ObservableMap._wrap(_context, _list.asMap(), _atom);
173+
Map<int, T> asMap() =>
174+
ObservableMap._wrap(_context, _list.asMap(), _atom, _equals);
171175

172176
@override
173-
List<R> cast<R>() => ObservableList._wrap(_context, _atom, _list.cast<R>());
177+
List<R> cast<R>([EqualityComparer<R>? equals]) => ObservableList._wrap(
178+
_context,
179+
_atom,
180+
_list.cast<R>(),
181+
equals ??
182+
(_equals != null
183+
? (R? a, R? b) => _equals!(a as T?, b as T?)
184+
: null));
174185

175186
@override
176187
List<T> toList({bool growable = true}) {
@@ -184,7 +195,7 @@ class ObservableList<T>
184195
set first(T value) {
185196
_context.conditionallyRunInAction(() {
186197
final oldValue = _list.first;
187-
if (oldValue != value) {
198+
if (_areEquals(oldValue, value)) {
188199
_list.first = value;
189200
_notifyElementUpdate(0, value, oldValue);
190201
}
@@ -376,7 +387,7 @@ class ObservableList<T>
376387
for (var i = 0; i < _list.length; ++i) {
377388
final oldValue = oldList[i];
378389
final newValue = _list[i];
379-
if (newValue != oldValue) {
390+
if (_areEquals(oldValue, newValue)) {
380391
changes.add(ElementChange(
381392
index: i, oldValue: oldValue, newValue: newValue));
382393
}
@@ -398,7 +409,7 @@ class ObservableList<T>
398409
for (var i = 0; i < _list.length; ++i) {
399410
final oldValue = oldList[i];
400411
final newValue = _list[i];
401-
if (newValue != oldValue) {
412+
if (_areEquals(oldValue, newValue)) {
402413
changes.add(ElementChange(
403414
index: i, oldValue: oldValue, newValue: newValue));
404415
}
@@ -456,6 +467,14 @@ class ObservableList<T>
456467

457468
_listeners.notifyListeners(change);
458469
}
470+
471+
bool _areEquals(T? a, T? b) {
472+
if (_equals != null) {
473+
return _equals!(a, b);
474+
} else {
475+
return equatable(a, b);
476+
}
477+
}
459478
}
460479

461480
typedef ListChangeListener<TNotification> = void Function(
@@ -520,4 +539,4 @@ class ListChange<T> {
520539
/// Used during testing for wrapping a regular `List<T>` as an `ObservableList<T>`
521540
@visibleForTesting
522541
ObservableList<T> wrapInObservableList<T>(Atom atom, List<T> list) =>
523-
ObservableList._wrap(mainContext, atom, list);
542+
ObservableList._wrap(mainContext, atom, list, null);

mobx/lib/src/api/observable_collections/observable_map.dart

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,37 +26,45 @@ class ObservableMap<K, V>
2626
MapMixin<K, V>
2727
implements
2828
Listenable<MapChange<K, V>> {
29-
ObservableMap({ReactiveContext? context, String? name})
29+
ObservableMap(
30+
{ReactiveContext? context, String? name, EqualityComparer<V>? equals})
3031
: _context = context ?? mainContext,
3132
_atom = _observableMapAtom<K, V>(context, name),
32-
_map = <K, V>{};
33+
_map = <K, V>{},
34+
_equals = equals;
3335

34-
ObservableMap.of(Map<K, V> other, {ReactiveContext? context, String? name})
36+
ObservableMap.of(Map<K, V> other,
37+
{ReactiveContext? context, String? name, EqualityComparer<V>? equals})
3538
: _context = context ?? mainContext,
3639
_atom = _observableMapAtom<K, V>(context, name),
37-
_map = Map.of(other);
40+
_map = Map.of(other),
41+
_equals = equals;
3842

3943
ObservableMap.linkedHashMapFrom(Map<K, V> other,
40-
{ReactiveContext? context, String? name})
44+
{ReactiveContext? context, String? name, EqualityComparer<V>? equals})
4145
: _context = context ?? mainContext,
4246
_atom = _observableMapAtom<K, V>(context, name),
43-
_map = LinkedHashMap.from(other);
47+
_map = LinkedHashMap.from(other),
48+
_equals = equals;
4449

4550
ObservableMap.splayTreeMapFrom(Map<K, V> other,
4651
{int Function(K, K)? compare,
4752
// ignore: avoid_annotating_with_dynamic
4853
bool Function(dynamic)? isValidKey,
4954
ReactiveContext? context,
50-
String? name})
55+
String? name,
56+
EqualityComparer<V>? equals})
5157
: _context = context ?? mainContext,
5258
_atom = _observableMapAtom<K, V>(context, name),
53-
_map = SplayTreeMap.from(other, compare, isValidKey);
59+
_map = SplayTreeMap.from(other, compare, isValidKey),
60+
_equals = equals;
5461

55-
ObservableMap._wrap(this._context, this._map, this._atom);
62+
ObservableMap._wrap(this._context, this._map, this._atom, this._equals);
5663

5764
final ReactiveContext _context;
5865
final Atom _atom;
5966
final Map<K, V> _map;
67+
final EqualityComparer<V>? _equals;
6068

6169
Map<K, V> get nonObservableInner => _map;
6270

@@ -94,7 +102,7 @@ class ObservableMap<K, V>
94102
}
95103
}
96104

97-
if (!_map.containsKey(key) || value != oldValue) {
105+
if (!_map.containsKey(key) || _areEquals(value, oldValue)) {
98106
_map[key] = value;
99107
if (type == 'update') {
100108
_reportUpdate(key, value, oldValue);
@@ -127,8 +135,15 @@ class ObservableMap<K, V>
127135
Iterable<K> get keys => MapKeysIterable(_map.keys, _atom);
128136

129137
@override
130-
Map<RK, RV> cast<RK, RV>() =>
131-
ObservableMap._wrap(_context, super.cast(), _atom);
138+
Map<RK, RV> cast<RK, RV>([EqualityComparer<RV>? equals]) =>
139+
ObservableMap._wrap(
140+
_context,
141+
super.cast(),
142+
_atom,
143+
equals ??
144+
(_equals == null
145+
? null
146+
: (RV? a, RV? b) => _equals!(a as V?, b as V?)));
132147

133148
@override
134149
V? remove(Object? key) {
@@ -231,13 +246,21 @@ class ObservableMap<K, V>
231246
}
232247
return _listeners.add(listener);
233248
}
249+
250+
bool _areEquals(V? a, V? b) {
251+
if (_equals != null) {
252+
return _equals!(a, b);
253+
} else {
254+
return equatable(a, b);
255+
}
256+
}
234257
}
235258

236259
/// A convenience method to wrap the standard `Map<K,V>` in an `ObservableMap<K,V>`.
237260
/// This is mostly to aid in testing.
238261
@visibleForTesting
239262
ObservableMap<K, V> wrapInObservableMap<K, V>(Atom atom, Map<K, V> map) =>
240-
ObservableMap._wrap(mainContext, map, atom);
263+
ObservableMap._wrap(mainContext, map, atom, null);
241264

242265
typedef MapChangeListener<K, V> = void Function(MapChange<K, V>);
243266

0 commit comments

Comments
 (0)