Hi guys, I have been working with mobx.dart over the past few days trying to port a mobx-state-tree app and store.
I must say it works great! Excellent job you guys did here. Thank you.
After solving encoding and decoding the store as well as finding a solution for dealing with references in encoded json form I now want to be able to auto-save the store on change.
In fact our setup consists of one root store and several sub stores each with their own persistend backend since one store holds tons of static objects and the others are filled with user data that reference these static objects.
I am looking for a way to detect a change either in any observable in the tree and preferably to detect a change while being able to determine which of the sub stores changed (the user one or the static one that allows very in frequent changes as well).
My thinking is that somehow I should use the mainContext to do this but cant figure out how to.
My preferred solution would be to listen for updates to any observable at the sub store level.
Hope this makes sense.
You may want to look at autorun, reaction, or when.
Thanks @rrousselGit, will try to make that work.
So it seems each observable has to be observed for change semi-individually.
I guess to do this per store one would add a https://mobx.pub/api/reaction#reaction and in its predicate simply reference all the observables in that store.
abstract class _Foo with Store {
@observer
String bar;
@observer
bool isTrue;
final _dispose = reaction((_) {
bar; // forces _$barAtom.reportObserved(); to be called
isTrue; // forces _$isTrueAtom.reportObserved(); to be called
}, (msg) => notifyAutoSave());
void dispose() {
_dispose();
}
}
Edit (for the reader): so the above code is wrong scroll down plz
For reactions, you have to return a value that will be compared with the previous value. If they differ, the effect-function (in your case the auto-save) will re-run.
Thank you @pavanpodila
Do you believe the most effective way to observe an entire store of observables and nested observables and stores is through a reaction?
Having at least 25 different store classes and hundreds of observables this would create for a lot of boiler plate code.
Could you think of a way to do that at a higher level lowering the chance of any single reaction having a coding error?
An any reaction could be interesting:
ReactionDisposer any(Store store, void cb());
mobx_codegen could generate a method on Store that calls reportObserved on all atoms.
Then any could use that method.
That's actually a neat idea @rrousselGit.
@dmdeklerk, you can have hundreds of dependencies for your reaction and it should be fine. MobX is smart enough to react only when needed and doesn't monitor those observables. It's entirely driven by notifications and only if a connected observable changes, will the reaction re-run. So its efficient from a scalability stand point.
To make it easier for you to automatically do this for your Store, you may want to look at a codegen-based approach, possibly by annotating the observables that should participate in the auto-save behavior. Something like:
@autosave('firstName')
@observable
String firstName;
@autosave('last') // this name can be changed
@observable
String lastName;
The codegen will generate a method called autosave() that will include the reaction you need. I think this will save you the grunt work.
Wow thank you @pavanpodila
Thanks for the tip on source generation @pavanpodila.
It seems to be doable to implement that https://medium.com/flutter-community/part-2-code-generation-in-dart-annotations-source-gen-and-build-runner-bbceee28697b (and clearly outside the scope of this project :D).
What is more in scope would be the code below... So given the above discussion would you say below is the correct approach to add support for basic autosave feature?
import 'package:mobx/mobx.dart';
/// https://mobx.pub/api/reaction#reaction
class AutoSave {
var _disposers = [];
void dispose() {
_disposers.forEach((d) => d());
}
void watch(observable) {
_disposers.add(
reaction((_) => observable, (_) => _autoSave())
);
}
_autoSave() {
/// do whatever to notify the auto saver
/// optionally pass a reference or identifier so we know which master store
/// this belongs to.
}
}
class MyStore extends _MyStore with _$MyStore;
abstract class _MyStore extends AutoSave with Store {
MyStore() {
/// we call watch on each observer that we want to trigger autosave
watch(name);
watch(age);
watch(cars);
watch(foo);
}
@observable
String name;
@observable
int age;
@observable
ObservableList cars = new ObservableList();
var foo = Observable('foo');
}
Edit: edited the code to be more correct for any other new people to dart/mobx/flutter.
Conceptually yes. You can pick and choose what needs to be watched. Direction looks correct. The @autosave annotation can simplify the registration of the _watched_ observables.
Is it reasonable if I make a PR for that any reaction, or should I try to make it as a package?
That could be very useful combined with another generator to make a store immutable.
I think the @autosave thing is an add-on to mobx, so it should be a separate package to keep the concerns separate.
The AutoSave class seems to work and seems sufficient for now as we are just learning Dart.
The solution mentioned in comment https://github.com/mobxjs/mobx.dart/issues/218#issuecomment-509991708 would definitively make things easier.
@rrousselGit Did you make a package for the any reaction?
@rrousselGit Please let's implement any reaction, I really tired to write boilerplate in my models.
An
anyreaction could be interesting:ReactionDisposer any(Store store, void cb());mobx_codegen could generate a method on Store that calls
reportObservedon all atoms.
Thenanycould use that method.
Cannot we write the any reaction as something like this:
dart
ReactionDisposer any(
List<dynamic Function(Reaction)> fns,
void Function() effect, {
String name,
int delay,
bool fireImmediately,
ReactiveContext context,
void Function(Object, Reaction) onError,
}) {
Set<dynamic> fn(Reaction reaction) => fns.map((f) => f(reaction)).toSet();
return reaction<Set<dynamic>>(
fn,
(c) => effect(),
delay: delay,
name: name,
fireImmediately: fireImmediately,
context: context,
equals: (dynamic set1, dynamic set2) {
return SetEquality().equals(set1, set2);
},
onError: onError,
);
Of course, we lose the equals on the individual fns, but maybe the above method can be extended to take into account this case too.
This does not solve the verbosity of the method above but it may work. The verbosity can be solved when the code generated store will have something like a list of all Observables!
Hi guys, so is that any reaction implemented? I have created new issu discribing my problem: https://github.com/mobxjs/mobx.dart/issues/635
this any reaction would be saver.
Or maybe there is another way how you are solving this because i believe problem i described shoold be quite common.
Most helpful comment
An
anyreaction could be interesting:mobx_codegen could generate a method on Store that calls
reportObservedon all atoms.Then
anycould use that method.