From 880ee282884226e7473ae63c89d6e9d8c9f95ec8 Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Tue, 12 Jul 2022 20:59:51 +0800 Subject: [PATCH 01/19] add `nonObservableInner`, such that users can bypass observability system for performance --- mobx/lib/src/api/observable_collections/observable_list.dart | 2 ++ mobx/lib/src/api/observable_collections/observable_map.dart | 2 ++ mobx/lib/src/api/observable_collections/observable_set.dart | 2 ++ 3 files changed, 6 insertions(+) diff --git a/mobx/lib/src/api/observable_collections/observable_list.dart b/mobx/lib/src/api/observable_collections/observable_list.dart index 692bc1bc7..1175127c4 100644 --- a/mobx/lib/src/api/observable_collections/observable_list.dart +++ b/mobx/lib/src/api/observable_collections/observable_list.dart @@ -40,6 +40,8 @@ class ObservableList final Atom _atom; final List _list; + List get nonObservableInner => _list; + Listeners>? _listenersField; Listeners> get _listeners => diff --git a/mobx/lib/src/api/observable_collections/observable_map.dart b/mobx/lib/src/api/observable_collections/observable_map.dart index 842e9fff6..be1cd7d4d 100644 --- a/mobx/lib/src/api/observable_collections/observable_map.dart +++ b/mobx/lib/src/api/observable_collections/observable_map.dart @@ -58,6 +58,8 @@ class ObservableMap final Atom _atom; final Map _map; + Map get nonObservableInner => _map; + String get name => _atom.name; Listeners>? _listenersField; diff --git a/mobx/lib/src/api/observable_collections/observable_set.dart b/mobx/lib/src/api/observable_collections/observable_set.dart index 520bf495c..663d449e7 100644 --- a/mobx/lib/src/api/observable_collections/observable_set.dart +++ b/mobx/lib/src/api/observable_collections/observable_set.dart @@ -61,6 +61,8 @@ class ObservableSet final Atom _atom; final Set _set; + Set get nonObservableInner => _set; + String get name => _atom.name; Listeners>? _listenersField; From e1df03a9b67c106893559dea3e2ea123c39434fd Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Sun, 4 Sep 2022 16:30:22 +0800 Subject: [PATCH 02/19] chore: format code --- mobx/lib/src/api/extensions.dart | 2 +- mobx/test/extensions/primitive_types_extensions_test.dart | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/mobx/lib/src/api/extensions.dart b/mobx/lib/src/api/extensions.dart index 8fa856bc4..8aae69d34 100644 --- a/mobx/lib/src/api/extensions.dart +++ b/mobx/lib/src/api/extensions.dart @@ -8,4 +8,4 @@ part 'extensions/observable_future_extension.dart'; part 'extensions/observable_list_extension.dart'; part 'extensions/observable_set_extension.dart'; part 'extensions/observable_map_extension.dart'; -part 'extensions/primitive_types_extensions.dart'; \ No newline at end of file +part 'extensions/primitive_types_extensions.dart'; diff --git a/mobx/test/extensions/primitive_types_extensions_test.dart b/mobx/test/extensions/primitive_types_extensions_test.dart index 9eda3244d..bbc32ae3b 100644 --- a/mobx/test/extensions/primitive_types_extensions_test.dart +++ b/mobx/test/extensions/primitive_types_extensions_test.dart @@ -32,6 +32,5 @@ void main() { flag.toggle(); expect(flag.value, equals(true)); }); - }); } From bbd9020eef582e334b3070940697168dc0598579 Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Sun, 4 Sep 2022 16:30:34 +0800 Subject: [PATCH 03/19] feat: Add `onDispose` callback to `Computed`, such that old values can be properly disposed #857 --- mobx/lib/src/core/computed.dart | 51 ++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/mobx/lib/src/core/computed.dart b/mobx/lib/src/core/computed.dart index 79929519f..084c824d1 100644 --- a/mobx/lib/src/core/computed.dart +++ b/mobx/lib/src/core/computed.dart @@ -31,16 +31,27 @@ class Computed extends Atom implements Derivation, ObservableValue { /// A computed value is _cached_ and it recomputes only when the dependent observables actually /// change. This makes them fast and you are free to use them throughout your application. Internally /// MobX uses a 2-phase change propagation that ensures no unnecessary computations are performed. - factory Computed(T Function() fn, - {String? name, - ReactiveContext? context, - EqualityComparer? equals}) => - Computed._(context ?? mainContext, fn, name: name, equals: equals); - - Computed._(ReactiveContext context, this._fn, {String? name, this.equals}) + factory Computed( + T Function() fn, { + String? name, + ReactiveContext? context, + EqualityComparer? equals, + void Function(T)? disposeValue, + }) => + Computed._( + context ?? mainContext, + fn, + (v) => v == null ? null : disposeValue?.call(v), + name: name, + equals: equals, + ); + + Computed._(ReactiveContext context, this._fn, this._disposeValue, + {String? name, this.equals}) : super._(context, name: name ?? context.nameFor('Computed')); final EqualityComparer? equals; + final void Function(T?) _disposeValue; @override MobXCaughtException? _errorValue; @@ -61,10 +72,19 @@ class Computed extends Atom implements Derivation, ObservableValue { // ignore: prefer_final_fields DerivationState _dependenciesState = DerivationState.notTracking; - T? _value; - bool _isComputing = false; + T? get _cachedValue => __cachedValue; + + // do NOT directly set `__cachedValue`. Instead, set `_cachedValue`, + // which will properly dispose the old value. + T? __cachedValue; + + set _cachedValue(T? newValue) { + _disposeValue(__cachedValue); + __cachedValue = newValue; + } + @override T get value { if (_isComputing) { @@ -75,7 +95,7 @@ class Computed extends Atom implements Derivation, ObservableValue { if (!_context.isWithinBatch && _observers.isEmpty) { if (_context._shouldCompute(this)) { _context.startBatch(); - _value = computeValue(track: false); + _cachedValue = computeValue(track: false); _context.endBatch(); } } else { @@ -91,7 +111,7 @@ class Computed extends Atom implements Derivation, ObservableValue { throw _errorValue!; } - return _value as T; + return _cachedValue as T; } T? computeValue({required bool track}) { @@ -123,7 +143,7 @@ class Computed extends Atom implements Derivation, ObservableValue { @override void _suspend() { _context._clearObservables(this); - _value = null; + _cachedValue = null; } @override @@ -136,7 +156,7 @@ class Computed extends Atom implements Derivation, ObservableValue { _context.spyReport(ComputedValueSpyEvent(this, name: name)); } - final oldValue = _value; + final oldValue = _cachedValue; final wasSuspended = _dependenciesState == DerivationState.notTracking; final hadCaughtException = _context._hasCaughtException(this); @@ -148,7 +168,10 @@ class Computed extends Atom implements Derivation, ObservableValue { wasSuspended || changedException || !_isEqual(oldValue, newValue); if (changed) { - _value = newValue; + _cachedValue = newValue; + } else { + // the new value is not used, so we should dispose it + _disposeValue(newValue); } return changed; From 263155b5eac3a95d7874a077bc8c7356219cdfa6 Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Sun, 4 Sep 2022 16:34:35 +0800 Subject: [PATCH 04/19] test: add tests to `disposeValue` #857 --- mobx/test/computed_test.dart | 98 ++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/mobx/test/computed_test.dart b/mobx/test/computed_test.dart index 143b9e1ef..8b67d473c 100644 --- a/mobx/test/computed_test.dart +++ b/mobx/test/computed_test.dart @@ -233,5 +233,103 @@ void main() { expect(value, equals('SUCCESS')); expect(error, isNull); }); + + group('dispose values', () { + late Observable observableThatAffectsValue; + late Observable observableThatDoesNotAffectValue; + late Computed computed; + late int computedCallCount; + late List disposeValueArguments; + setUp(() { + observableThatAffectsValue = Observable('A1'); + observableThatDoesNotAffectValue = Observable('something'); + computedCallCount = 0; + disposeValueArguments = []; + computed = Computed( + () { + observableThatDoesNotAffectValue.value; // just access it, but does not affect computed's value + return '${observableThatAffectsValue.value}-Computed#${computedCallCount++}'; + }, + disposeValue: disposeValueArguments.add, + ); + }); + + group('when the computation is called twice, should dispose the value generated in the first computation', () { + test('when access Computed twice *outside* reactive environment', () { + expect(computedCallCount, 0); + expect(disposeValueArguments, const []); + + expect(computed.value, 'A1-Computed#0'); + + expect(computedCallCount, 1); + expect(disposeValueArguments, const []); + + expect(computed.value, 'A1-Computed#1'); + + expect(computedCallCount, 2); + expect(disposeValueArguments, const ['A1-Computed#0']); + }); + + group('when Computed\'s dependency changes', () { + test('and when the computed value changes', () { + expect(computedCallCount, 0); + expect(disposeValueArguments, const []); + + late String computedValue; + final autorunDisposer = autorun((_) => computedValue = computed.value); + expect(computedValue, 'A1-Computed#0'); + + runInAction(() => observableThatAffectsValue.value = 'A2'); + + expect(computedValue, 'A2-Computed#1'); + expect(computedCallCount, 2); + expect(disposeValueArguments, const ['A1-Computed#0']); + + autorunDisposer(); + + expect(computedCallCount, 2); + expect(disposeValueArguments, const ['A1-Computed#0', 'A2-Computed#1']); + }); + + test('and when the computed value does not change', () { + expect(computedCallCount, 0); + expect(disposeValueArguments, const []); + + late String computedValue; + final autorunDisposer = autorun((_) => computedValue = computed.value); + expect(computedValue, 'A1-Computed#0'); + + runInAction(() => observableThatDoesNotAffectValue.value = 'something else but this does not matter'); + + expect(computedValue, 'A1-Computed#1'); + expect(computedCallCount, 2); + expect(disposeValueArguments, const ['A1-Computed#0']); + + autorunDisposer(); + + expect(computedCallCount, 2); + expect(disposeValueArguments, const ['A1-Computed#0', 'A1-Computed#1']); + }); + }); + }); + + // indeed, via `_suspend` + test('when nobody is observing the Computed, should dispose the value', () { + expect(computedCallCount, 0); + expect(disposeValueArguments, const []); + + late final String computedValue; + final autorunDisposer = autorun((_) => computedValue = computed.value); + expect(computedValue, 'A1-Computed#0'); + + expect(computedCallCount, 1); + expect(disposeValueArguments, const []); + + autorunDisposer(); + + expect(computedCallCount, 1); + expect(disposeValueArguments, const ['A1-Computed#0']); + }); + }); }); } From 396cd934873e1031936833eed1c5871cb009212d Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Sun, 4 Sep 2022 16:45:59 +0800 Subject: [PATCH 05/19] test: add tests for "Allow users to bypass observability system for performance" --- mobx/test/computed_test.dart | 25 +++++++++++++++++-------- mobx/test/observable_list_test.dart | 17 +++++++++++++++++ mobx/test/observable_map_test.dart | 16 ++++++++++++++++ mobx/test/observable_set_test.dart | 17 +++++++++++++++++ 4 files changed, 67 insertions(+), 8 deletions(-) diff --git a/mobx/test/computed_test.dart b/mobx/test/computed_test.dart index 8b67d473c..5954b073a 100644 --- a/mobx/test/computed_test.dart +++ b/mobx/test/computed_test.dart @@ -247,14 +247,17 @@ void main() { disposeValueArguments = []; computed = Computed( () { - observableThatDoesNotAffectValue.value; // just access it, but does not affect computed's value + observableThatDoesNotAffectValue + .value; // just access it, but does not affect computed's value return '${observableThatAffectsValue.value}-Computed#${computedCallCount++}'; }, disposeValue: disposeValueArguments.add, ); }); - group('when the computation is called twice, should dispose the value generated in the first computation', () { + group( + 'when the computation is called twice, should dispose the value generated in the first computation', + () { test('when access Computed twice *outside* reactive environment', () { expect(computedCallCount, 0); expect(disposeValueArguments, const []); @@ -276,7 +279,8 @@ void main() { expect(disposeValueArguments, const []); late String computedValue; - final autorunDisposer = autorun((_) => computedValue = computed.value); + final autorunDisposer = + autorun((_) => computedValue = computed.value); expect(computedValue, 'A1-Computed#0'); runInAction(() => observableThatAffectsValue.value = 'A2'); @@ -288,7 +292,8 @@ void main() { autorunDisposer(); expect(computedCallCount, 2); - expect(disposeValueArguments, const ['A1-Computed#0', 'A2-Computed#1']); + expect(disposeValueArguments, + const ['A1-Computed#0', 'A2-Computed#1']); }); test('and when the computed value does not change', () { @@ -296,10 +301,12 @@ void main() { expect(disposeValueArguments, const []); late String computedValue; - final autorunDisposer = autorun((_) => computedValue = computed.value); + final autorunDisposer = + autorun((_) => computedValue = computed.value); expect(computedValue, 'A1-Computed#0'); - runInAction(() => observableThatDoesNotAffectValue.value = 'something else but this does not matter'); + runInAction(() => observableThatDoesNotAffectValue.value = + 'something else but this does not matter'); expect(computedValue, 'A1-Computed#1'); expect(computedCallCount, 2); @@ -308,13 +315,15 @@ void main() { autorunDisposer(); expect(computedCallCount, 2); - expect(disposeValueArguments, const ['A1-Computed#0', 'A1-Computed#1']); + expect(disposeValueArguments, + const ['A1-Computed#0', 'A1-Computed#1']); }); }); }); // indeed, via `_suspend` - test('when nobody is observing the Computed, should dispose the value', () { + test('when nobody is observing the Computed, should dispose the value', + () { expect(computedCallCount, 0); expect(disposeValueArguments, const []); diff --git a/mobx/test/observable_list_test.dart b/mobx/test/observable_list_test.dart index 9383ffce8..e0a750a57 100644 --- a/mobx/test/observable_list_test.dart +++ b/mobx/test/observable_list_test.dart @@ -626,6 +626,23 @@ void main() { '[]': (_) => _[0], '+': (_) => _ + [100], }.forEach(_templateReadTest); + + test('bypass observable system', () { + final list = ObservableList(); + + int? nonObservableInnerLength; + autorun( + (_) => nonObservableInnerLength = list.nonObservableInner.length); + + expect(list.nonObservableInner.length, 0); + expect(nonObservableInnerLength, equals(0)); + + list.add(20); + + expect(list.nonObservableInner.length, 1); + expect(nonObservableInnerLength, equals(0), + reason: 'should not be observable'); + }); }); }); diff --git a/mobx/test/observable_map_test.dart b/mobx/test/observable_map_test.dart index 01d740eb4..b057d83e8 100644 --- a/mobx/test/observable_map_test.dart +++ b/mobx/test/observable_map_test.dart @@ -228,6 +228,22 @@ void main() { expect(map.containsKey('a'), isTrue); }); + + test('bypass observable system', () { + final map = ObservableMap(); + + int? nonObservableInnerLength; + autorun((_) => nonObservableInnerLength = map.nonObservableInner.length); + + expect(map.nonObservableInner.length, 0); + expect(nonObservableInnerLength, equals(0)); + + map[10] = 20; + + expect(map.nonObservableInner.length, 1); + expect(nonObservableInnerLength, equals(0), + reason: 'should not be observable'); + }); }); } diff --git a/mobx/test/observable_set_test.dart b/mobx/test/observable_set_test.dart index 9fb119ed6..3e88ba59a 100644 --- a/mobx/test/observable_set_test.dart +++ b/mobx/test/observable_set_test.dart @@ -1,4 +1,5 @@ import 'package:mobx/src/api/observable_collections.dart'; +import 'package:mobx/src/api/reaction.dart'; import 'package:mobx/src/core.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; @@ -138,6 +139,22 @@ void main() { 'retainWhere': (m) => m.retainWhere((i) => i < 3), }.forEach(runWriteTest); }); + + test('bypass observable system', () { + final set = ObservableSet(); + + int? nonObservableInnerLength; + autorun((_) => nonObservableInnerLength = set.nonObservableInner.length); + + expect(set.nonObservableInner.length, 0); + expect(nonObservableInnerLength, equals(0)); + + set.add(10); + + expect(set.nonObservableInner.length, 1); + expect(nonObservableInnerLength, equals(0), + reason: 'should not be observable'); + }); }); } From 20fe5d3471aa7c8b931599be623ebee212df2baf Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Sun, 4 Sep 2022 16:53:05 +0800 Subject: [PATCH 06/19] feat: add `Computed.dispose` #859 --- mobx/lib/src/core/computed.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mobx/lib/src/core/computed.dart b/mobx/lib/src/core/computed.dart index 084c824d1..8304efc21 100644 --- a/mobx/lib/src/core/computed.dart +++ b/mobx/lib/src/core/computed.dart @@ -140,6 +140,10 @@ class Computed extends Atom implements Derivation, ObservableValue { return value; } + void dispose() { + _suspend(); + } + @override void _suspend() { _context._clearObservables(this); From 63d57efee52f3464046a1c0917c557387daeed8f Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Sun, 4 Sep 2022 16:56:08 +0800 Subject: [PATCH 07/19] test: add tests for Computed.dispose --- mobx/test/computed_test.dart | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/mobx/test/computed_test.dart b/mobx/test/computed_test.dart index 5954b073a..156ed75c2 100644 --- a/mobx/test/computed_test.dart +++ b/mobx/test/computed_test.dart @@ -256,7 +256,7 @@ void main() { }); group( - 'when the computation is called twice, should dispose the value generated in the first computation', + 'when the computation is called twice, should dispose the unused value', () { test('when access Computed twice *outside* reactive environment', () { expect(computedCallCount, 0); @@ -339,6 +339,40 @@ void main() { expect(computedCallCount, 1); expect(disposeValueArguments, const ['A1-Computed#0']); }); + + group('when call Computed.dispose()', () { + test('when nobody was observing it, should do nothing', () { + computed.dispose(); + expect(computedCallCount, 0); + expect(disposeValueArguments, const []); + }); + + test( + 'when somebody was observing it, should still dispose the current value', + () { + expect(computedCallCount, 0); + expect(disposeValueArguments, const []); + + late final String computedValue; + final autorunDisposer = + autorun((_) => computedValue = computed.value); + expect(computedValue, 'A1-Computed#0'); + + expect(computedCallCount, 1); + expect(disposeValueArguments, const []); + + // NOTE this + computed.dispose(); + + expect(computedCallCount, 1); + expect(disposeValueArguments, const ['A1-Computed#0']); + + autorunDisposer(); + + expect(computedCallCount, 1); + expect(disposeValueArguments, const ['A1-Computed#0']); + }); + }); }); }); } From ada24f75be25421601b9773f8f791b5baa64a3f0 Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Mon, 5 Sep 2022 21:55:56 +0800 Subject: [PATCH 08/19] test: reproduce #855 --- mobx/test/atom_extensions_test.dart | 61 +++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 mobx/test/atom_extensions_test.dart diff --git a/mobx/test/atom_extensions_test.dart b/mobx/test/atom_extensions_test.dart new file mode 100644 index 000000000..003f07fda --- /dev/null +++ b/mobx/test/atom_extensions_test.dart @@ -0,0 +1,61 @@ +import 'package:mobx/mobx.dart'; +import 'package:test/test.dart'; + +void main() { + test( + 'when write to @observable field with changed value, should trigger notifications for downstream', + () { + final store = _ExampleStore(); + + final autorunResults = []; + autorun((_) => autorunResults.add(store.value)); + + expect(autorunResults, ['first']); + + store.value = 'second'; + + expect(autorunResults, ['first', 'second']); + }); + + // fixed by #855 + test( + 'when write to @observable field with unchanged value, should not trigger notifications for downstream', + () { + final store = _ExampleStore(); + + final autorunResults = []; + autorun((_) => autorunResults.add(store.value)); + + expect(autorunResults, ['first']); + + store.value = store.value; + + expect(autorunResults, ['first']); + }); +} + +class _ExampleStore = __ExampleStore with _$_ExampleStore; + +abstract class __ExampleStore with Store { + @observable + String value = 'first'; +} + +// This is what typically a mobx codegen will generate. +mixin _$_ExampleStore on __ExampleStore, Store { + // ignore: non_constant_identifier_names + late final _$valueAtom = Atom(name: '__ExampleStore.value', context: context); + + @override + String get value { + _$valueAtom.reportRead(); + return super.value; + } + + @override + set value(String value) { + _$valueAtom.reportWrite(value, super.value, () { + super.value = value; + }); + } +} From 3b930399262d0e5bebe09517bac25e57cc0b4e9a Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Mon, 5 Sep 2022 21:57:38 +0800 Subject: [PATCH 09/19] fix: Avoid unnecessary observable notifications of `@observable` fields of `Store`s #855 --- mobx/lib/src/core/atom_extensions.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mobx/lib/src/core/atom_extensions.dart b/mobx/lib/src/core/atom_extensions.dart index ed2a117de..d0b3498ad 100644 --- a/mobx/lib/src/core/atom_extensions.dart +++ b/mobx/lib/src/core/atom_extensions.dart @@ -7,6 +7,11 @@ extension AtomSpyReporter on Atom { } void reportWrite(T newValue, T oldValue, void Function() setNewValue) { + // Avoid unnecessary observable notifications of @observable fields of Stores + if (newValue == oldValue) { + return; + } + context.spyReport(ObservableValueSpyEvent(this, newValue: newValue, oldValue: oldValue, name: name)); From 305bd4ee1db6d882753ece8e694b327d8feb824b Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Tue, 6 Sep 2022 08:36:05 +0800 Subject: [PATCH 10/19] Revert "test: add tests for Computed.dispose" This reverts commit 63d57efee52f3464046a1c0917c557387daeed8f. --- mobx/test/computed_test.dart | 36 +----------------------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/mobx/test/computed_test.dart b/mobx/test/computed_test.dart index 156ed75c2..5954b073a 100644 --- a/mobx/test/computed_test.dart +++ b/mobx/test/computed_test.dart @@ -256,7 +256,7 @@ void main() { }); group( - 'when the computation is called twice, should dispose the unused value', + 'when the computation is called twice, should dispose the value generated in the first computation', () { test('when access Computed twice *outside* reactive environment', () { expect(computedCallCount, 0); @@ -339,40 +339,6 @@ void main() { expect(computedCallCount, 1); expect(disposeValueArguments, const ['A1-Computed#0']); }); - - group('when call Computed.dispose()', () { - test('when nobody was observing it, should do nothing', () { - computed.dispose(); - expect(computedCallCount, 0); - expect(disposeValueArguments, const []); - }); - - test( - 'when somebody was observing it, should still dispose the current value', - () { - expect(computedCallCount, 0); - expect(disposeValueArguments, const []); - - late final String computedValue; - final autorunDisposer = - autorun((_) => computedValue = computed.value); - expect(computedValue, 'A1-Computed#0'); - - expect(computedCallCount, 1); - expect(disposeValueArguments, const []); - - // NOTE this - computed.dispose(); - - expect(computedCallCount, 1); - expect(disposeValueArguments, const ['A1-Computed#0']); - - autorunDisposer(); - - expect(computedCallCount, 1); - expect(disposeValueArguments, const ['A1-Computed#0']); - }); - }); }); }); } From 3912572b694dde4262ea92b4fd732ddc49050af4 Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Tue, 6 Sep 2022 08:36:09 +0800 Subject: [PATCH 11/19] Revert "feat: add `Computed.dispose` #859" This reverts commit 20fe5d3471aa7c8b931599be623ebee212df2baf. --- mobx/lib/src/core/computed.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mobx/lib/src/core/computed.dart b/mobx/lib/src/core/computed.dart index 8304efc21..084c824d1 100644 --- a/mobx/lib/src/core/computed.dart +++ b/mobx/lib/src/core/computed.dart @@ -140,10 +140,6 @@ class Computed extends Atom implements Derivation, ObservableValue { return value; } - void dispose() { - _suspend(); - } - @override void _suspend() { _context._clearObservables(this); From 290f8c3deff123035e1a13eeb1d131cb9224f375 Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Tue, 6 Sep 2022 08:36:41 +0800 Subject: [PATCH 12/19] Revert "test: add tests to `disposeValue` #857" This reverts commit 263155b5eac3a95d7874a077bc8c7356219cdfa6. # Conflicts: # mobx/test/computed_test.dart --- mobx/test/computed_test.dart | 107 ----------------------------------- 1 file changed, 107 deletions(-) diff --git a/mobx/test/computed_test.dart b/mobx/test/computed_test.dart index 5954b073a..143b9e1ef 100644 --- a/mobx/test/computed_test.dart +++ b/mobx/test/computed_test.dart @@ -233,112 +233,5 @@ void main() { expect(value, equals('SUCCESS')); expect(error, isNull); }); - - group('dispose values', () { - late Observable observableThatAffectsValue; - late Observable observableThatDoesNotAffectValue; - late Computed computed; - late int computedCallCount; - late List disposeValueArguments; - setUp(() { - observableThatAffectsValue = Observable('A1'); - observableThatDoesNotAffectValue = Observable('something'); - computedCallCount = 0; - disposeValueArguments = []; - computed = Computed( - () { - observableThatDoesNotAffectValue - .value; // just access it, but does not affect computed's value - return '${observableThatAffectsValue.value}-Computed#${computedCallCount++}'; - }, - disposeValue: disposeValueArguments.add, - ); - }); - - group( - 'when the computation is called twice, should dispose the value generated in the first computation', - () { - test('when access Computed twice *outside* reactive environment', () { - expect(computedCallCount, 0); - expect(disposeValueArguments, const []); - - expect(computed.value, 'A1-Computed#0'); - - expect(computedCallCount, 1); - expect(disposeValueArguments, const []); - - expect(computed.value, 'A1-Computed#1'); - - expect(computedCallCount, 2); - expect(disposeValueArguments, const ['A1-Computed#0']); - }); - - group('when Computed\'s dependency changes', () { - test('and when the computed value changes', () { - expect(computedCallCount, 0); - expect(disposeValueArguments, const []); - - late String computedValue; - final autorunDisposer = - autorun((_) => computedValue = computed.value); - expect(computedValue, 'A1-Computed#0'); - - runInAction(() => observableThatAffectsValue.value = 'A2'); - - expect(computedValue, 'A2-Computed#1'); - expect(computedCallCount, 2); - expect(disposeValueArguments, const ['A1-Computed#0']); - - autorunDisposer(); - - expect(computedCallCount, 2); - expect(disposeValueArguments, - const ['A1-Computed#0', 'A2-Computed#1']); - }); - - test('and when the computed value does not change', () { - expect(computedCallCount, 0); - expect(disposeValueArguments, const []); - - late String computedValue; - final autorunDisposer = - autorun((_) => computedValue = computed.value); - expect(computedValue, 'A1-Computed#0'); - - runInAction(() => observableThatDoesNotAffectValue.value = - 'something else but this does not matter'); - - expect(computedValue, 'A1-Computed#1'); - expect(computedCallCount, 2); - expect(disposeValueArguments, const ['A1-Computed#0']); - - autorunDisposer(); - - expect(computedCallCount, 2); - expect(disposeValueArguments, - const ['A1-Computed#0', 'A1-Computed#1']); - }); - }); - }); - - // indeed, via `_suspend` - test('when nobody is observing the Computed, should dispose the value', - () { - expect(computedCallCount, 0); - expect(disposeValueArguments, const []); - - late final String computedValue; - final autorunDisposer = autorun((_) => computedValue = computed.value); - expect(computedValue, 'A1-Computed#0'); - - expect(computedCallCount, 1); - expect(disposeValueArguments, const []); - - autorunDisposer(); - - expect(computedCallCount, 1); - expect(disposeValueArguments, const ['A1-Computed#0']); - }); - }); }); } From 424ec02341b1b801437ad70fbd379f9144d8c63d Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Tue, 6 Sep 2022 08:36:48 +0800 Subject: [PATCH 13/19] Revert "feat: Add `onDispose` callback to `Computed`, such that old values can be properly disposed #857" This reverts commit bbd9020eef582e334b3070940697168dc0598579. --- mobx/lib/src/core/computed.dart | 51 +++++++++------------------------ 1 file changed, 14 insertions(+), 37 deletions(-) diff --git a/mobx/lib/src/core/computed.dart b/mobx/lib/src/core/computed.dart index 084c824d1..79929519f 100644 --- a/mobx/lib/src/core/computed.dart +++ b/mobx/lib/src/core/computed.dart @@ -31,27 +31,16 @@ class Computed extends Atom implements Derivation, ObservableValue { /// A computed value is _cached_ and it recomputes only when the dependent observables actually /// change. This makes them fast and you are free to use them throughout your application. Internally /// MobX uses a 2-phase change propagation that ensures no unnecessary computations are performed. - factory Computed( - T Function() fn, { - String? name, - ReactiveContext? context, - EqualityComparer? equals, - void Function(T)? disposeValue, - }) => - Computed._( - context ?? mainContext, - fn, - (v) => v == null ? null : disposeValue?.call(v), - name: name, - equals: equals, - ); - - Computed._(ReactiveContext context, this._fn, this._disposeValue, - {String? name, this.equals}) + factory Computed(T Function() fn, + {String? name, + ReactiveContext? context, + EqualityComparer? equals}) => + Computed._(context ?? mainContext, fn, name: name, equals: equals); + + Computed._(ReactiveContext context, this._fn, {String? name, this.equals}) : super._(context, name: name ?? context.nameFor('Computed')); final EqualityComparer? equals; - final void Function(T?) _disposeValue; @override MobXCaughtException? _errorValue; @@ -72,18 +61,9 @@ class Computed extends Atom implements Derivation, ObservableValue { // ignore: prefer_final_fields DerivationState _dependenciesState = DerivationState.notTracking; - bool _isComputing = false; - - T? get _cachedValue => __cachedValue; - - // do NOT directly set `__cachedValue`. Instead, set `_cachedValue`, - // which will properly dispose the old value. - T? __cachedValue; + T? _value; - set _cachedValue(T? newValue) { - _disposeValue(__cachedValue); - __cachedValue = newValue; - } + bool _isComputing = false; @override T get value { @@ -95,7 +75,7 @@ class Computed extends Atom implements Derivation, ObservableValue { if (!_context.isWithinBatch && _observers.isEmpty) { if (_context._shouldCompute(this)) { _context.startBatch(); - _cachedValue = computeValue(track: false); + _value = computeValue(track: false); _context.endBatch(); } } else { @@ -111,7 +91,7 @@ class Computed extends Atom implements Derivation, ObservableValue { throw _errorValue!; } - return _cachedValue as T; + return _value as T; } T? computeValue({required bool track}) { @@ -143,7 +123,7 @@ class Computed extends Atom implements Derivation, ObservableValue { @override void _suspend() { _context._clearObservables(this); - _cachedValue = null; + _value = null; } @override @@ -156,7 +136,7 @@ class Computed extends Atom implements Derivation, ObservableValue { _context.spyReport(ComputedValueSpyEvent(this, name: name)); } - final oldValue = _cachedValue; + final oldValue = _value; final wasSuspended = _dependenciesState == DerivationState.notTracking; final hadCaughtException = _context._hasCaughtException(this); @@ -168,10 +148,7 @@ class Computed extends Atom implements Derivation, ObservableValue { wasSuspended || changedException || !_isEqual(oldValue, newValue); if (changed) { - _cachedValue = newValue; - } else { - // the new value is not used, so we should dispose it - _disposeValue(newValue); + _value = newValue; } return changed; From fcc222b44ec0a14fc1bd9b4afa72065bf0c33e1b Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Tue, 6 Sep 2022 13:58:01 +0800 Subject: [PATCH 14/19] format code and minor change to code --- mobx/lib/src/api/async/async_action.dart | 2 +- mobx/lib/src/api/extensions/primitive_types_extensions.dart | 2 +- mobx/lib/src/core/atom.dart | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mobx/lib/src/api/async/async_action.dart b/mobx/lib/src/api/async/async_action.dart index 0f8872986..f8b98cb93 100644 --- a/mobx/lib/src/api/async/async_action.dart +++ b/mobx/lib/src/api/async/async_action.dart @@ -52,7 +52,7 @@ class AsyncAction { R _runUnary( Zone self, ZoneDelegate parent, Zone zone, R Function(A a) f, A a) { final actionInfo = - _actions.startAction(name: '${_actions.name}(Zone.runUnary)'); + _actions.startAction(name: '${_actions.name}(Zone.runUnary)'); try { final result = parent.runUnary(zone, f, a); return result; diff --git a/mobx/lib/src/api/extensions/primitive_types_extensions.dart b/mobx/lib/src/api/extensions/primitive_types_extensions.dart index 42ede749b..571fe5d43 100644 --- a/mobx/lib/src/api/extensions/primitive_types_extensions.dart +++ b/mobx/lib/src/api/extensions/primitive_types_extensions.dart @@ -33,4 +33,4 @@ extension StringExtension on String { Observable obs({ReactiveContext? context, String? name}) { return Observable(this, context: context, name: name); } -} \ No newline at end of file +} diff --git a/mobx/lib/src/core/atom.dart b/mobx/lib/src/core/atom.dart index 8614a8129..655d189c6 100644 --- a/mobx/lib/src/core/atom.dart +++ b/mobx/lib/src/core/atom.dart @@ -42,6 +42,8 @@ class Atom { DerivationState _lowestObserverState = DerivationState.notTracking; + bool get isBeingObserved => _isBeingObserved; + // ignore: prefer_final_fields bool _isBeingObserved = false; From 637d12b2d65cffb6b6b8a6886ec528f39fdb9368 Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Wed, 7 Sep 2022 08:28:37 +0800 Subject: [PATCH 15/19] add Observable.nonObservableValue --- mobx/lib/src/core/observable.dart | 2 ++ mobx/test/observable_test.dart | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/mobx/lib/src/core/observable.dart b/mobx/lib/src/core/observable.dart index 4f7f99482..318f474d7 100644 --- a/mobx/lib/src/core/observable.dart +++ b/mobx/lib/src/core/observable.dart @@ -53,6 +53,8 @@ class Observable extends Atom return _value; } + T get nonObservableValue => _value; + set value(T value) { _context.enforceWritePolicy(this); diff --git a/mobx/test/observable_test.dart b/mobx/test/observable_test.dart index fe9ea5a92..e31641de0 100644 --- a/mobx/test/observable_test.dart +++ b/mobx/test/observable_test.dart @@ -59,6 +59,14 @@ void main() { () => context.endBatch() ]); }); + + test('nonObservableValue', () { + final x = Observable(null); + expect(x.nonObservableValue, null); + + x.value = 100; + expect(x.nonObservableValue, 100); + }); }); group('createAtom()', () { From e852b9bb3bdbd1d91138440d89fb91185e2d4457 Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Thu, 8 Sep 2022 13:54:46 +0800 Subject: [PATCH 16/19] fix: Reaction lacks toString, so cannot see which reaction causes the error #864 --- mobx/lib/src/core/reaction.dart | 3 +++ mobx/test/reaction_test.dart | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/mobx/lib/src/core/reaction.dart b/mobx/lib/src/core/reaction.dart index 5ba9468fb..706a83b62 100644 --- a/mobx/lib/src/core/reaction.dart +++ b/mobx/lib/src/core/reaction.dart @@ -180,4 +180,7 @@ class ReactionImpl implements Reaction { _context._notifyReactionErrorHandlers(exception, this); } + + @override + String toString() => 'Reaction($name)'; } diff --git a/mobx/test/reaction_test.dart b/mobx/test/reaction_test.dart index a920c1cce..abe714e35 100644 --- a/mobx/test/reaction_test.dart +++ b/mobx/test/reaction_test.dart @@ -13,6 +13,11 @@ void main() { testSetup(); group('Reaction', () { + test('toString', () { + final r = ReactionImpl(mainContext, () => null, name: 'MyName'); + expect(r.toString(), 'Reaction(MyName)'); + }); + test('basics work', () { var executed = false; final x = Observable(10); From 75260bf9620c551719ddf863f149de9427af90ce Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Thu, 8 Sep 2022 14:11:18 +0800 Subject: [PATCH 17/19] feat: Add StackTrace to reactions in debug mode to easily spot which reaction it is #864 --- mobx/lib/src/core/context.dart | 4 +++- mobx/lib/src/core/reaction.dart | 11 ++++++++++- mobx/test/reaction_test.dart | 7 ++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/mobx/lib/src/core/context.dart b/mobx/lib/src/core/context.dart index bf36b86ac..ab73fe5a6 100644 --- a/mobx/lib/src/core/context.dart +++ b/mobx/lib/src/core/context.dart @@ -342,7 +342,9 @@ class ReactiveContext { _resetState(); throw MobXCyclicReactionException( - "Reaction doesn't converge to a stable state after ${config.maxIterations} iterations. Probably there is a cycle in the reactive function: $failingReaction"); + "Reaction doesn't converge to a stable state after ${config.maxIterations} iterations. " + "Probably there is a cycle in the reactive function: $failingReaction " + "(creation stack: ${failingReaction.debugCreationStack})"); } final remainingReactions = allReactions.toList(growable: false); diff --git a/mobx/lib/src/core/reaction.dart b/mobx/lib/src/core/reaction.dart index 706a83b62..cc53539df 100644 --- a/mobx/lib/src/core/reaction.dart +++ b/mobx/lib/src/core/reaction.dart @@ -6,6 +6,8 @@ abstract class Reaction implements Derivation { void dispose(); void _run(); + + StackTrace? get debugCreationStack; } class ReactionImpl implements Reaction { @@ -13,6 +15,11 @@ class ReactionImpl implements Reaction { {required this.name, void Function(Object, Reaction)? onError}) { _onInvalidate = onInvalidate; _onError = onError; + + assert(() { + debugCreationStack = StackTrace.current; + return true; + }()); } void Function(Object, ReactionImpl)? _onError; @@ -23,6 +30,8 @@ class ReactionImpl implements Reaction { bool _isDisposed = false; bool _isRunning = false; + late final StackTrace? debugCreationStack; + @override final String name; @@ -182,5 +191,5 @@ class ReactionImpl implements Reaction { } @override - String toString() => 'Reaction($name)'; + String toString() => 'Reaction(name: $name)'; } diff --git a/mobx/test/reaction_test.dart b/mobx/test/reaction_test.dart index abe714e35..a8394b68a 100644 --- a/mobx/test/reaction_test.dart +++ b/mobx/test/reaction_test.dart @@ -15,7 +15,12 @@ void main() { group('Reaction', () { test('toString', () { final r = ReactionImpl(mainContext, () => null, name: 'MyName'); - expect(r.toString(), 'Reaction(MyName)'); + expect(r.toString(), contains('MyName')); + }); + + test('debugCreationStack', () { + final r = ReactionImpl(mainContext, () => null, name: 'MyName'); + expect(r.debugCreationStack, isNotNull); }); test('basics work', () { From fecb442a2c9ef9c3f369d5f2ee19026592fb0405 Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Thu, 8 Sep 2022 14:43:42 +0800 Subject: [PATCH 18/19] fix linter errors --- mobx/lib/src/core/reaction.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/mobx/lib/src/core/reaction.dart b/mobx/lib/src/core/reaction.dart index cc53539df..958a835fd 100644 --- a/mobx/lib/src/core/reaction.dart +++ b/mobx/lib/src/core/reaction.dart @@ -30,6 +30,7 @@ class ReactionImpl implements Reaction { bool _isDisposed = false; bool _isRunning = false; + @override late final StackTrace? debugCreationStack; @override From 1803928507e994c7ac4c50575f62417afe0e8425 Mon Sep 17 00:00:00 2001 From: fzyzcjy Date: Thu, 8 Sep 2022 18:36:58 +0800 Subject: [PATCH 19/19] add toString and debugCreationStack --- mobx/lib/src/core/action.dart | 6 +++++- mobx/lib/src/core/atom.dart | 5 ++++- mobx/lib/src/core/computed.dart | 4 ++++ mobx/lib/src/core/observable.dart | 4 ++++ mobx/lib/src/core/reaction.dart | 13 +++---------- mobx/lib/src/utils.dart | 16 ++++++++++++++++ mobx/test/action_test.dart | 13 +++++++++++++ mobx/test/computed_test.dart | 13 +++++++++++++ mobx/test/observable_test.dart | 13 +++++++++++++ mobx/test/reaction_test.dart | 11 +++++++---- 10 files changed, 82 insertions(+), 16 deletions(-) diff --git a/mobx/lib/src/core/action.dart b/mobx/lib/src/core/action.dart index d01cae8d4..63789d96b 100644 --- a/mobx/lib/src/core/action.dart +++ b/mobx/lib/src/core/action.dart @@ -1,6 +1,6 @@ part of '../core.dart'; -class Action { +class Action with DebugCreationStack { /// Creates an action that encapsulates all the mutations happening on the /// observables. /// @@ -61,6 +61,10 @@ class Action { _controller.endAction(runInfo); } } + + @override + String toString() => + 'Action(name: $name, identity: ${identityHashCode(this)})'; } /// `ActionController` is used to define the start/end boundaries of code which diff --git a/mobx/lib/src/core/atom.dart b/mobx/lib/src/core/atom.dart index 655d189c6..fdb116fa7 100644 --- a/mobx/lib/src/core/atom.dart +++ b/mobx/lib/src/core/atom.dart @@ -5,7 +5,7 @@ enum _ListenerKind { onBecomeUnobserved, } -class Atom { +class Atom with DebugCreationStack { /// Creates a simple Atom for tracking its usage in a reactive context. This is useful when /// you don't need the value but instead a way of knowing when it becomes active and inactive /// in a reaction. @@ -116,6 +116,9 @@ class Atom { } }; } + + @override + String toString() => 'Atom(name: $name, identity: ${identityHashCode(this)})'; } class WillChangeNotification { diff --git a/mobx/lib/src/core/computed.dart b/mobx/lib/src/core/computed.dart index 79929519f..8045997ad 100644 --- a/mobx/lib/src/core/computed.dart +++ b/mobx/lib/src/core/computed.dart @@ -181,4 +181,8 @@ class Computed extends Atom implements Derivation, ObservableValue { }, context: _context) .call; } + + @override + String toString() => + 'Computed<$T>(name: $name, identity: ${identityHashCode(this)})'; } diff --git a/mobx/lib/src/core/observable.dart b/mobx/lib/src/core/observable.dart index 318f474d7..a29230479 100644 --- a/mobx/lib/src/core/observable.dart +++ b/mobx/lib/src/core/observable.dart @@ -127,4 +127,8 @@ class Observable extends Atom @override Dispose intercept(Interceptor interceptor) => _interceptors.add(interceptor); + + @override + String toString() => + 'Observable<$T>(name: $name, identity: ${identityHashCode(this)})'; } diff --git a/mobx/lib/src/core/reaction.dart b/mobx/lib/src/core/reaction.dart index 958a835fd..18afc9324 100644 --- a/mobx/lib/src/core/reaction.dart +++ b/mobx/lib/src/core/reaction.dart @@ -10,16 +10,11 @@ abstract class Reaction implements Derivation { StackTrace? get debugCreationStack; } -class ReactionImpl implements Reaction { +class ReactionImpl with DebugCreationStack implements Reaction { ReactionImpl(this._context, Function() onInvalidate, {required this.name, void Function(Object, Reaction)? onError}) { _onInvalidate = onInvalidate; _onError = onError; - - assert(() { - debugCreationStack = StackTrace.current; - return true; - }()); } void Function(Object, ReactionImpl)? _onError; @@ -30,9 +25,6 @@ class ReactionImpl implements Reaction { bool _isDisposed = false; bool _isRunning = false; - @override - late final StackTrace? debugCreationStack; - @override final String name; @@ -192,5 +184,6 @@ class ReactionImpl implements Reaction { } @override - String toString() => 'Reaction(name: $name)'; + String toString() => + 'Reaction(name: $name, identity: ${identityHashCode(this)})'; } diff --git a/mobx/lib/src/utils.dart b/mobx/lib/src/utils.dart index efbff8905..cd81d806b 100644 --- a/mobx/lib/src/utils.dart +++ b/mobx/lib/src/utils.dart @@ -4,3 +4,19 @@ const Duration ms = Duration(milliseconds: 1); Timer Function(void Function()) createDelayedScheduler(int delayMs) => (fn) => Timer(ms * delayMs, fn); + +mixin DebugCreationStack { + /// Set the flag to true, to enable [debugCreationStack]. + /// Otherwise, the stack is always null. + static var enable = false; + + /// The stack trace when the object is created + final StackTrace? debugCreationStack = () { + StackTrace? result; + assert(() { + if (enable) result = StackTrace.current; + return true; + }()); + return result; + }(); +} diff --git a/mobx/test/action_test.dart b/mobx/test/action_test.dart index 64bb7e81e..84acc91f9 100644 --- a/mobx/test/action_test.dart +++ b/mobx/test/action_test.dart @@ -1,4 +1,5 @@ import 'package:mobx/mobx.dart'; +import 'package:mobx/src/utils.dart'; import 'package:mocktail/mocktail.dart' as mock; import 'package:test/test.dart'; @@ -11,6 +12,18 @@ void main() { testSetup(); group('Action', () { + test('toString', () { + final object = Action(() {}, name: 'MyName'); + expect(object.toString(), contains('MyName')); + }); + + test('debugCreationStack', () { + DebugCreationStack.enable = true; + addTearDown(() => DebugCreationStack.enable = false); + final object = Action(() {}); + expect(object.debugCreationStack, isNotNull); + }); + test('basics work', () { final a = Action((String name, String value) { expect(name, equals('name')); diff --git a/mobx/test/computed_test.dart b/mobx/test/computed_test.dart index 143b9e1ef..8668cb559 100644 --- a/mobx/test/computed_test.dart +++ b/mobx/test/computed_test.dart @@ -1,5 +1,6 @@ import 'package:collection/collection.dart'; import 'package:mobx/mobx.dart' hide when; +import 'package:mobx/src/utils.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; @@ -10,6 +11,18 @@ void main() { testSetup(); group('Computed', () { + test('toString', () { + final object = Computed(() {}, name: 'MyName'); + expect(object.toString(), contains('MyName')); + }); + + test('debugCreationStack', () { + DebugCreationStack.enable = true; + addTearDown(() => DebugCreationStack.enable = false); + final object = Computed(() {}); + expect(object.debugCreationStack, isNotNull); + }); + test('basics work', () { final x = Observable(20); final y = Observable(10); diff --git a/mobx/test/observable_test.dart b/mobx/test/observable_test.dart index e31641de0..8e54f3a3c 100644 --- a/mobx/test/observable_test.dart +++ b/mobx/test/observable_test.dart @@ -1,4 +1,5 @@ import 'package:mobx/mobx.dart'; +import 'package:mobx/src/utils.dart'; import 'package:mocktail/mocktail.dart' as mock; import 'package:test/test.dart'; @@ -12,6 +13,18 @@ void main() { testSetup(); group('observable', () { + test('toString', () { + final object = Observable(42, name: 'MyName'); + expect(object.toString(), contains('MyName')); + }); + + test('debugCreationStack', () { + DebugCreationStack.enable = true; + addTearDown(() => DebugCreationStack.enable = false); + final object = Observable(42); + expect(object.debugCreationStack, isNotNull); + }); + test('basics work', () { final x = Observable(null); expect(x.value, equals(null)); diff --git a/mobx/test/reaction_test.dart b/mobx/test/reaction_test.dart index a8394b68a..cb97bd0c8 100644 --- a/mobx/test/reaction_test.dart +++ b/mobx/test/reaction_test.dart @@ -1,6 +1,7 @@ import 'package:fake_async/fake_async.dart'; import 'package:mobx/mobx.dart' hide when; import 'package:mobx/src/core.dart'; +import 'package:mobx/src/utils.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; @@ -14,13 +15,15 @@ void main() { group('Reaction', () { test('toString', () { - final r = ReactionImpl(mainContext, () => null, name: 'MyName'); - expect(r.toString(), contains('MyName')); + final object = ReactionImpl(mainContext, () => null, name: 'MyName'); + expect(object.toString(), contains('MyName')); }); test('debugCreationStack', () { - final r = ReactionImpl(mainContext, () => null, name: 'MyName'); - expect(r.debugCreationStack, isNotNull); + DebugCreationStack.enable = true; + addTearDown(() => DebugCreationStack.enable = false); + final object = ReactionImpl(mainContext, () => null, name: 'MyName'); + expect(object.debugCreationStack, isNotNull); }); test('basics work', () {