Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow a custom equals parameter for observable collections #970

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions mobx/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.2.4

- Allow a custom equals parameter for observable collections( ObservableList, ObservableMap, ObservableSet ) - [@amondnet](https://github.com/amondnet)

## 2.2.3

- 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)
Expand Down
2 changes: 2 additions & 0 deletions mobx/lib/src/api/observable_collections.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'dart:collection';
import 'dart:math';

import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
import 'package:mobx/mobx.dart';
import 'package:mobx/src/utils.dart';

part 'observable_collections/observable_list.dart';
part 'observable_collections/observable_map.dart';
Expand Down
43 changes: 31 additions & 12 deletions mobx/lib/src/api/observable_collections/observable_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,23 @@
ListMixin<T>
implements
Listenable<ListChange<T>> {
ObservableList({ReactiveContext? context, String? name})
: this._wrap(context, _observableListAtom<T>(context, name), []);
ObservableList(
{ReactiveContext? context, String? name, EqualityComparer<T>? equals})
: this._wrap(context, _observableListAtom<T>(context, name), [], equals);

ObservableList.of(Iterable<T> elements,
{ReactiveContext? context, String? name})
{ReactiveContext? context, String? name, EqualityComparer<T>? equals})
: this._wrap(context, _observableListAtom<T>(context, name),
List<T>.of(elements, growable: true));
List<T>.of(elements, growable: true), equals);

ObservableList._wrap(ReactiveContext? context, this._atom, this._list)
ObservableList._wrap(
ReactiveContext? context, this._atom, this._list, this._equals)
: _context = context ?? mainContext;

final ReactiveContext _context;
final Atom _atom;
final List<T> _list;
final EqualityComparer<T>? _equals;

List<T> get nonObservableInner => _list;

Expand Down Expand Up @@ -96,7 +99,7 @@
_context.conditionallyRunInAction(() {
final oldValue = _list[index];

if (oldValue != value) {
if (!_areEquals(oldValue, value)) {
_list[index] = value;
_notifyElementUpdate(index, value, oldValue);
}
Expand Down Expand Up @@ -167,10 +170,18 @@
}

@override
Map<int, T> asMap() => ObservableMap._wrap(_context, _list.asMap(), _atom);
Map<int, T> asMap() =>
ObservableMap._wrap(_context, _list.asMap(), _atom, _equals);

@override
List<R> cast<R>() => ObservableList._wrap(_context, _atom, _list.cast<R>());
List<R> cast<R>([EqualityComparer<R>? equals]) => ObservableList._wrap(
_context,
_atom,
_list.cast<R>(),
equals ??
(_equals != null
? (R? a, R? b) => _equals!(a as T?, b as T?)

Check warning on line 183 in mobx/lib/src/api/observable_collections/observable_list.dart

View check run for this annotation

Codecov / codecov/patch

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

Added line #L183 was not covered by tests
: null));

@override
List<T> toList({bool growable = true}) {
Expand All @@ -184,7 +195,7 @@
set first(T value) {
_context.conditionallyRunInAction(() {
final oldValue = _list.first;
if (oldValue != value) {
if (!_areEquals(oldValue, value)) {
_list.first = value;
_notifyElementUpdate(0, value, oldValue);
}
Expand Down Expand Up @@ -376,7 +387,7 @@
for (var i = 0; i < _list.length; ++i) {
final oldValue = oldList[i];
final newValue = _list[i];
if (newValue != oldValue) {
if (!_areEquals(oldValue, newValue)) {
changes.add(ElementChange(
index: i, oldValue: oldValue, newValue: newValue));
}
Expand All @@ -398,7 +409,7 @@
for (var i = 0; i < _list.length; ++i) {
final oldValue = oldList[i];
final newValue = _list[i];
if (newValue != oldValue) {
if (!_areEquals(oldValue, newValue)) {
changes.add(ElementChange(
index: i, oldValue: oldValue, newValue: newValue));
}
Expand Down Expand Up @@ -456,6 +467,14 @@

_listeners.notifyListeners(change);
}

bool _areEquals(T? a, T? b) {
if (_equals != null) {
return _equals!(a, b);

Check warning on line 473 in mobx/lib/src/api/observable_collections/observable_list.dart

View check run for this annotation

Codecov / codecov/patch

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

Added line #L473 was not covered by tests
} else {
return equatable(a, b);
}
}
}

typedef ListChangeListener<TNotification> = void Function(
Expand Down Expand Up @@ -520,4 +539,4 @@
/// Used during testing for wrapping a regular `List<T>` as an `ObservableList<T>`
@visibleForTesting
ObservableList<T> wrapInObservableList<T>(Atom atom, List<T> list) =>
ObservableList._wrap(mainContext, atom, list);
ObservableList._wrap(mainContext, atom, list, null);
49 changes: 36 additions & 13 deletions mobx/lib/src/api/observable_collections/observable_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,37 +26,45 @@
MapMixin<K, V>
implements
Listenable<MapChange<K, V>> {
ObservableMap({ReactiveContext? context, String? name})
ObservableMap(
{ReactiveContext? context, String? name, EqualityComparer<V>? equals})
: _context = context ?? mainContext,
_atom = _observableMapAtom<K, V>(context, name),
_map = <K, V>{};
_map = <K, V>{},
_equals = equals;

ObservableMap.of(Map<K, V> other, {ReactiveContext? context, String? name})
ObservableMap.of(Map<K, V> other,
{ReactiveContext? context, String? name, EqualityComparer<V>? equals})
: _context = context ?? mainContext,
_atom = _observableMapAtom<K, V>(context, name),
_map = Map.of(other);
_map = Map.of(other),
_equals = equals;

ObservableMap.linkedHashMapFrom(Map<K, V> other,
{ReactiveContext? context, String? name})
{ReactiveContext? context, String? name, EqualityComparer<V>? equals})
: _context = context ?? mainContext,
_atom = _observableMapAtom<K, V>(context, name),
_map = LinkedHashMap.from(other);
_map = LinkedHashMap.from(other),
_equals = equals;

ObservableMap.splayTreeMapFrom(Map<K, V> other,
{int Function(K, K)? compare,
// ignore: avoid_annotating_with_dynamic
bool Function(dynamic)? isValidKey,
ReactiveContext? context,
String? name})
String? name,
EqualityComparer<V>? equals})
: _context = context ?? mainContext,
_atom = _observableMapAtom<K, V>(context, name),
_map = SplayTreeMap.from(other, compare, isValidKey);
_map = SplayTreeMap.from(other, compare, isValidKey),
_equals = equals;

ObservableMap._wrap(this._context, this._map, this._atom);
ObservableMap._wrap(this._context, this._map, this._atom, this._equals);

final ReactiveContext _context;
final Atom _atom;
final Map<K, V> _map;
final EqualityComparer<V>? _equals;

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

Expand Down Expand Up @@ -94,7 +102,7 @@
}
}

if (!_map.containsKey(key) || value != oldValue) {
if (!_map.containsKey(key) || !_areEquals(value, oldValue)) {
_map[key] = value;
if (type == 'update') {
_reportUpdate(key, value, oldValue);
Expand Down Expand Up @@ -127,8 +135,15 @@
Iterable<K> get keys => MapKeysIterable(_map.keys, _atom);

@override
Map<RK, RV> cast<RK, RV>() =>
ObservableMap._wrap(_context, super.cast(), _atom);
Map<RK, RV> cast<RK, RV>([EqualityComparer<RV>? equals]) =>
ObservableMap._wrap(
_context,
super.cast(),
_atom,
equals ??
(_equals == null
? null
: (RV? a, RV? b) => _equals!(a as V?, b as V?)));

Check warning on line 146 in mobx/lib/src/api/observable_collections/observable_map.dart

View check run for this annotation

Codecov / codecov/patch

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

Added line #L146 was not covered by tests

@override
V? remove(Object? key) {
Expand Down Expand Up @@ -231,13 +246,21 @@
}
return _listeners.add(listener);
}

bool _areEquals(V? a, V? b) {
if (_equals != null) {
return _equals!(a, b);

Check warning on line 252 in mobx/lib/src/api/observable_collections/observable_map.dart

View check run for this annotation

Codecov / codecov/patch

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

Added line #L252 was not covered by tests
} else {
return equatable(a, b);
}
}
}

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

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

Expand Down
Loading