Is there any difference if I use Provider's context.watch or context.read? AFAIK it is regarding ChangeNotifier only and doesn't matter for MobX Stores.
If I want to get observables from the store to use inside of a widget callback (like validate() or onTap()) - is it okay to get the store with store = context.read? Will I receive an observable's updates?
if you use Observer widget from the mobx_flutter package they track and receive changes when observables change automatically. At that point you are using Provider to look up a reference to an object (dependency injection) and not for its updating mechanism.
My personal opinion is that using Provider with Mobx is overkill and counterintuitive, it's a pity that the docs even promote it.
Provider is great as your application scales in size. It solves the Store dependency issue very nicely, whereas MobX solves the issue around reactivity and ensuring the UI updates as the Store changes. Both have their place and value and neither is solving the other's problem completely, hence the need for both exist.
Of course, we only recommend based on our experience, but would love to see if there are better ways of solving the Dependency Injection issue.
I used to use Provider for delivering MobX stores and get_it for accessing services like Retrofit API, Hive DB, Dio instance, etc. from Stores.
Of course, we only recommend based on our experience, but would love to see if there are better ways of solving the Dependency Injection issue.
I have a fairly complex app that I'm finishing up, with both Android and Windows support, using literally only mobx and a local database for all my state management needs and I don't have difficulties dealing with dependencies between components. I can't open source the project just yet, but once I finish I hope I can share some bits and pieces and show how I'm using Mobx.
Is my approach easier than Provider (or get_it)? Definitely not and I don't expect it to be widely adopted. But it also isn't like way more difficult. iI just requires a bit extra work(a few lines of extra code here and there) and discipline.
Without a doubt Provider and get_it are one of the easiest approaches, but they are easy (and "scales") because of how much it encourages bad habits. For example, the reason Provider "scales" well for large applications is because of the global shared state it promotes - on top of which they then it buildt a whole layer of abstraction to make this global state "contextual" and "compile safe", to look less like global state.
If you need any kind of dependency injection then probably get_it is a much better choice because at least it's not trying to do too much (which is changing lately, and it's going down the same path provider does). Honestly once I got comfortable with mobx and its features I simply removed get_it too because I didn't need any dependency injection anymore.
The way I'm developing my software is through this dreaded "manually passing state down the tree" and callbacks, which obviously is frowned upon in the flutter community.
This is what Flutter has to say about this technique.
This works okay, but for an app state that you need to [access and] modify from many different places, you鈥檇 have to pass around a lot of callbacks鈥攚hich gets old pretty quickly.
It gets old because it imposes a limitation on the developer on what they can do and how they must structure the code, and it doesn't let them write components that tap into some vague external state willy-nilly. It requires a greater amount of discipline and more effort spent on code structure. This limitation that is often actually beneficial for larger projects is then called "an approach that doesn't scale".
but for an app state that you need to modify from many different places
This sentence fills me with dread. If you have an app state that needs to be modified from 5-6 (or even up to 10 different places), then you don't need a library or "dependency injection" and you can just deal with it with normal code structure. However if your app "scales" to a point where the same state can be modified from 20-30+ different places and implementing and tracking dependencies through constructors and function parameters becomes cumbersome,
then you are definitely architecting your app wrong and your code will be a nightmare to maintain regardless if you use libraries that magically handles the dependencies for you.
I used to use
Providerfor delivering MobX stores andget_itfor accessing services like Retrofit API, Hive DB, Dio instance, etc. from Stores.
If you are not using Provider's Consumer and observer widgets, get_it can do the same thing for you as Provider's context.watch.
You get the advantage of removing all the Provider widgets from your tree - your presentation code(the widgets) is not polluted with code that have nothing to do with presentation.
Although using get_it to share state between the widgets has the same dangers as Provider - it promotes global state (and side effects).
Dependency injection only really make sense to access, like you said, services. Code that is not intrinsically tied to your state and presentation logic, but simply provides extra functionality to your view components.
For example, if you have a widget that wants to save a document, it makes sense to pull in a service (a DB class) whose sole responsabilty is to take an input document and save it somewhere. It's fine to have this configured, initialized at the start of your app and registered in a global scope (or even contextual if need be), and later rely on it from various widgets.
It's also fine to have separate services that do business logic validation, so your views (or their controllers) can take user input, pass it to your "validation" service take the response, pass it to the database service and then take whatever the result of the operation is and keep the users informed.
@Rudiksz with all these great comments you are just short of a blog post or a guide for the MobX site 馃. Would love to have you write a piece on the site for the benefit of everyone馃檹馃徏
I'm actually against the get_it singleton approach and all for provider.
But that doesn't really matter too much in practice.
If your app gets a little complex you are likely to want stores to be able to communicate with each other. You can use a store holder (or root store) class for that. One class that holds all (or most) of your MobX Stores, which passes a reference of itself to all the stores. The stores in the store holder can use that reference to talk to other stores.
So in practice, using this approach, this will have you passing just 1 instance (of the store holder) around.
https://mobx.js.org/defining-data-stores.html#combining-multiple-stores
Another advantage: Let's say you want reset all your stores in the app (i.e. change the user). You can also use the store holder instance to write a reset method that calls all the reset methods for those stores. Which keeps your code clean.
Creating a root store makes sense but Provider has the ability to load stores lazily but it lacks in terms of store-to-store communication. I hope Provider and/or Riverpod will add a feature for store-to-store communication.
It already has. You both just didn't figure it out.
In MobX you safely can do this:
Provider<AuthStore>(
lazy: false,
create: (ctx) => AuthStore()..load(),
),
Provider<ProfileStore>(
lazy: false,
create: (ctx) => ProfileStore(authStore: ctx.read<AuthStore>()),
dispose: (ctx, store) => store.dispose(),
),
You also can use ProxyProvider to make one store dependent to another and recreate it every time the first store ref updates.
But it's a one-directional link, you can only make your next store depend on previous. You can't access ProfileStore from AuthStore. So think your architecture in advance.
Unfortunately, your suggestion is also an example of one-directional and more importantly one-time communication. In other words, ProfileStore is created with the state of AuthStore at the instance of creation. For example, ProfileStore will not react to any change in AuthStore. AuthStore already does not have any idea about what is going on in ProfileStore. Store-to-store communication means two-way communication and two-way reactivity of stores.
Unfortunately, your suggestion is also an example of one-directional and more importantly one-time communication. In other words, ProfileStore is created with the state of AuthStore at the instance of creation.
No, that's not correct. Bidirectional link is always bad, because it makes your stores tightly coupled and dependent on each other. It is bad, because they cannot exist without each other. You should isolate your stores as much as you can. Google: "loose coupling", it is a common programming principle, not Flutter only.
If your "next" store depends on the state of "previous" store, it is a one-directional link, and it is okay. Usually, every store depends on current user or "isAuth" state in the AuthStore.
ProfileStore will not react to any change in AuthStore
That's not true too. If you use MobX, then when you change your observables and pass it via parameters to another store, it will always have an actual value of the observable variable, because you pass a reference, not a value.
If you use ProxyProvider, its 'update' callback recreates whole ProfileStore every time when AuthStore's variable reference changed.
You jumped to another topic. We were not discussing what is wrong or bad. But, anyway, thanks for your comments.
Most helpful comment
if you use Observer widget from the mobx_flutter package they track and receive changes when observables change automatically. At that point you are using Provider to look up a reference to an object (dependency injection) and not for its updating mechanism.
My personal opinion is that using Provider with Mobx is overkill and counterintuitive, it's a pity that the docs even promote it.