Skip to content

Commit

Permalink
Avoid unnecessary observable notifications of @observable `Iterable…
Browse files Browse the repository at this point in the history
…` or `Map` fields
  • Loading branch information
amondnet committed Nov 2, 2023
1 parent dc14b4c commit 74dba7d
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 4 deletions.
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.2

- Avoid unnecessary observable notifications of `@observable` `Iterable` or `Map` fields of Stores by [@amondnet](https://github.com/amondnet)

## 2.2.1

- Reduces unnecessary iterations while using `Iterables` with `addAll`, `insertAll`, `replaceRange`, `setAll` and `setRange` methods. [#942](https://github.com/mobxjs/mobx.dart/pull/942).
Expand Down
22 changes: 20 additions & 2 deletions mobx/lib/src/core/atom_extensions.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:mobx/mobx.dart';

extension AtomSpyReporter on Atom {
Expand All @@ -8,8 +9,9 @@ extension AtomSpyReporter on Atom {

void reportWrite<T>(T newValue, T oldValue, void Function() setNewValue,
{EqualityComparer<T>? equals}) {
final areEqual =
equals == null ? oldValue == newValue : equals(oldValue, newValue);
final areEqual = equals == null
? _equals(oldValue, newValue)
: equals(oldValue, newValue);

// Avoid unnecessary observable notifications of @observable fields of Stores
if (areEqual) {
Expand All @@ -31,3 +33,19 @@ extension AtomSpyReporter on Atom {
context.spyReport(EndedSpyEvent(type: 'observable', name: name));
}
}

/// Determines whether [a] and [b] are equal.
bool _equals<T>(T a, T b) {
if (identical(a, b)) return true;
if (a is Iterable || a is Map) {
if (!_equality.equals(a, b)) return false;
} else if (a.runtimeType != b.runtimeType) {
return false;
} else if (a != b) {
return false;
}

return true;
}

const DeepCollectionEquality _equality = DeepCollectionEquality();
4 changes: 2 additions & 2 deletions mobx/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: mobx
version: 2.2.1
version: 2.2.2
description: "MobX is a library for reactively managing the state of your applications. Use the power of observables, actions, and reactions to supercharge your Dart and Flutter apps."

homepage: https://github.com/mobxjs/mobx.dart
Expand All @@ -10,10 +10,10 @@ environment:

dependencies:
meta: ^1.3.0
collection: ^1.15.0

dev_dependencies:
build_runner: ^2.0.6
collection: ^1.15.0
coverage: ^1.0.1
fake_async: ^1.2.0
lints: ^2.0.0
Expand Down
86 changes: 86 additions & 0 deletions mobx/test/atom_extensions_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,54 @@ void main() {

expect(autorunResults, ['first', 'second']);
});

test(
'when write to @observable iterable field with unchanged value, should not trigger notifications for downstream',
() {
final store = _ExampleStore();

final autorunResults = <List<String>>[];
autorun((_) => autorunResults.add(store.list));

store.list = ['first'];
expect(autorunResults, [
['first']
]);

store.list = ['first'];
expect(autorunResults, [
['first']
]);

store.list = ['first'];
expect(autorunResults, [
['first']
]);
});

test(
'when write to @observable map field with unchanged value, should not trigger notifications for downstream',
() {
final store = _ExampleStore();

final autorunResults = <Map<String, int>>[];
autorun((_) => autorunResults.add(store.map));

store.map = {'first': 1};
expect(autorunResults, [
{'first': 1}
]);

store.map = {'first': 1};
expect(autorunResults, [
{'first': 1}
]);

store.map = {'first': 1};
expect(autorunResults, [
{'first': 1}
]);
});
}

class _ExampleStore = __ExampleStore with _$_ExampleStore;
Expand All @@ -83,6 +131,12 @@ abstract class __ExampleStore with Store {

@MakeObservable(equals: _equals)
String value3 = 'first';

@observable
List<String> list = ['first'];

@observable
Map<String, int> map = {'first': 1};
}

// This is what typically a mobx codegen will generate.
Expand Down Expand Up @@ -138,4 +192,36 @@ mixin _$_ExampleStore on __ExampleStore, Store {
equals: (String? oldValue, String? newValue) =>
oldValue?.length == newValue?.length);
}

// ignore: non_constant_identifier_names
late final _$listAtom = Atom(name: '__ExampleStore.list', context: context);

@override
List<String> get list {
_$listAtom.reportRead();
return super.list;
}

@override
set list(List<String> value) {
_$listAtom.reportWrite(value, super.list, () {
super.list = value;
});
}

// ignore: non_constant_identifier_names
late final _$mapAtom = Atom(name: '__ExampleStore.map', context: context);

@override
Map<String, int> get map {
_$mapAtom.reportRead();
return super.map;
}

@override
set map(Map<String, int> value) {
_$mapAtom.reportWrite(value, super.map, () {
super.map = value;
});
}
}

0 comments on commit 74dba7d

Please sign in to comment.