This is more about a theoretical discussion of flutter riverpod state management package. Currently I am trying to migrate one kinda huge project from Provider to Riverpod and I encountered an issue which was easily solved in Provider, but seems to be a "show stopper" for Riverpod.
So the issue is following.
Imagine that you are having a ClientDetailScreen that can be opened from multiple places of the app. The basic path to get there is ClientsTab > Search (click on searched name) > ClientDetailScreen opens. However you can also get there from a completely different tab, let's say a dashboard, where you have messages from the clients and when you click on their name, you get into the ClientDetailScreen as well. Significance of this is that there can be N of ClientDetailScreens open.
From the ClientDetailScreen you can access all kinds of subscreens that are related to this particular client with which you entered the screen. Now the issue arise.
Alright, so in order to access some information about this client across all possible subscreens with Provider package you created a ChangeNotifier on top level of ClientDetailScreen and that means every single screen had its own provider accessible. Meaning that in all subscreens and related widgets you could do:
context.read<ClientProvider>();
and you would access correct data everytime.
With Riverpod, this problem does not seem to be so easily solvable. Riverpod is way more strict about creating providers. Basically they are created as soon as they are used on a global level. I know about family constructor to differentiate between correct instance, however I am hesitant to choose that path especially when it would mean I would have to somehow distribute the clientId across all the subwidgets and that just does not seem right. I am more inclined to the fact that there is something fundamentally wrong with how the app is written. I was trying to solve it via doing a local ProviderScope that would encapsulate whole ClientDetailScreen hoping that all subwidgets would then access correct provider. However for some reason, all the subscreens are pushed on the same level as ClientDetailScreen meaning that they don't access the correct scope and because of some dependencies I am unable to place the ProviderScope even higher.
Have you ever solved similar problem? Thanks for any response.
In the end I managed to solve this by overriding provider in the ProviderContainer() itself as soon as I have clientData loaded. Then in the subwidgets I am acessing the data not via ref.read, but directly on the container which I expose via simple Provider. I am posting the solution in order to close this thread and maybe for a future reference for somebody in similar need.
Basically I prepare the container like this
final clientDetailProviderContainer = Provider<ProviderContainer>((ref) {
return ProviderContainer(
overrides: [
clientProvider.overrideWithValue(null), // We just do this to have the same number of overrides
],
);
});
On the clientDetailScreen where we load the clientData I override it:
...fetching data from BE -> Future -> FutureBuilder and then this:
final clientDetailProviderCont = ref.read(clientDetailProviderContainer);
clientDetailProviderCont.updateOverrides([
clientProvider.overrideWithValue(
ClientProvider(
currentlyOpenedClient: client
),
),
]);
Finally in the subscreens/widgets we can read it like this. Exclamation mark should be ofc in ideal case avoided as much as we can. In this case I know we have the data overriden.
late final client = ref.read(clientDetailProviderContainer).read(clientProvider)!.currentlyOpenedClient;
I know it is not ideal solution, however at the current state of the project, I think it is the only viable one without the need to refactor 50+ files and the whole loading logic.