Provider not accesable when Navigate to new screen - flutter

have a problem that I'm sitting on couple of days now.
have an app where:
depending of AUTH state, 'LoginScreen' or 'MainScreen' is Shown.
in MainScreen I setUp bottomNavigation with screens (HomeScreen, ShoppingScreen,MyFavorites)
I set up there as well my StreamProviders(those depend on Auth) by using MultiProvider
on HomeScreen when I User Provider.of(context) it works like it should
but when I use :
`Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ProfileScreen(),
),
);
` and use Provider.of(context) there I get "Could not find correct Provider....above this...widget"
I read some issues on that and solution there was to decler providers above MaterailApp which in my case I can not do because I can set up thoese only after Auth is successfull.
Tryed passing context(from HomeScreen) to ProfileScreen(through constructor) and that work but when value changed of UserData it did not update the screen (guessing beacause of diffrent 'contexts')
What am I doing wrong in here,any Ideas?:S

Providers are "scoped".
This means that if they are placed inside a screen, they aren't accessible outside that screen.
Which means that if a provider is scoped but needs to be accessed outside of the route it was created in, we have two solutions:
un-scope the provider. This involves moving the provider to a common ancestor of both widgets that needs to obtain the value.
If those two widgets are on two different Routes, then it basically mean "move the provider above MaterialApp/CupertinoApp.
manually pass the provider to the new screen (needed when using Navigator.push)
The idea is, instead of having one provider, we have two of them, both using the same value as explained here See How to scope a ChangeNotifier to some routes using Provider? for a practical example.
For Navigator.push, this can look like:
final myModel = Provider.of<MyModel>(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
ChangeNotifierProvider.value(
value: myModel,
child: MyScreen(),
),
),
);

Please make sure that you application's root widget is Provider Widget, it should event be the parent of MaterialWidget. If this is already the case I will need your code to look into. Something like this
class AppState {
User loggedInUser;
bool get isLoggedIn {
return loggedInUser != null;
}
// Other states as per the requirements
// ...
}

Related

Flutter: accessing providers from other providers

For my flutter project, I am using the following multiple providers below:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<FirstProvider>(
create: (context) => FirstProvider(),
),
ChangeNotifierProvider<SecondProvider>(
create: (context) => SecondProvider(),
),
ChangeNotifierProvider<ThirdProvider>(
create: (context) => ThirdProvider(),
),
ChangeNotifierProvider<FourthProvider>(
create: (context) => FourthProvider(),
),
],
child: const MainApp(),
);
}
Because sometimes I need to either get data or call functions from different providers from another provider, I am using it like this:
//First Provider
class FirstProvider with ChangeNotifier {
void callFunctionFromSecondProvider({
required BuildContext context,
}) {
//Access the SecondProvider
final secondProvider= Provider.of<SecondProvider>(
context,
listen: false,
);
secondProvider.myFunction();
}
}
//Second Provider
class SecondProvider with ChangeNotifier {
bool _currentValue = true;
void myFunction(){
//Do something
}
}
The callFunctionFromSecondProvider()of the FirstProvider is called from a widget and it will call myFunction() successfully, most of times.
Depending on the complexity of the function, I am sometimes experiencing that I can't access the SecondProvider, presumably due to context being null, when the widget state changes.
I am reading some documents online regarding provider, and they are suggesting changenotifierproxyprovider for what I understood as 1 to 1 provider relationship.
However, in my case, one provider needs to be accessed by multiple providers and vice versa.
Question:
Is there a more appropriate way that I can approach my case where one provider can be accessed by multiple providers?
EDIT:
Accessing provider should also be able to access different variable values without creating a new instance.
Instead of passing context to the callFunctionFromSecondProvider function add the second provider as the parameter. So the function looks like the below.
Not sure this is the correct way of doing that but my context null issue was fixed this way.
void callFunctionFromSecondProvider({
required SecondProvider secondProvider,
}) {
secondProvider.myFunction();
}
}
Alright.
So it looks like Riverpod by the same author is the way to go as it addresses alot of flaws such as Provider being dependent on the widget tree, in my case, where the underlying issue came from.
—--------
For the time being, I still need to use the provider and for a quick and dirty solution, I am providing the context of not only the current widget that I am trying to access the provider, but also passing the parent context of the widget directly, so that in case a modal (for example) is closed, then any subsequent provider call can still be executed using the parent context.
Hope this helps.

Provider not found Flutter

I want to share some data across different widgets so I decided to use a ChangeNotifierProvider<Example> with its relative Consumer<Example>. I have already used Providers before but never in this way (in fact I got some errors).
ChangeNotifierProvider<Example> has been defined in menu page while Consumer<Example> in an other widget defined in menu too.
Menu page :
class Menu extends StatefulWidget {
//...SOme code
ChangeNotifierProvider<Example>(
create: (context) => Example(),
child: ShowMultipleAnswers()
//...some code
And now I would like to use Consumer<Example> inside ShowMultipleAnswers() widget consuming data created in menu like :
class ShowMultipleAnswers extends StatefulWidget {
//...some code
Widget build(BuildContext context) {
return Consumer<Example>(builder: (context, handler, child) {
//some code
But I got these errors :
Error: Could not find the correct Provider<Example> above this Consumer<Example> Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- You added a new provider in your `main.dart` and performed a hot-reload.
To fix, perform a hot-restart.
- The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
- You used a `BuildContext` that is an ancestor of the provider you are trying to read.
consider using `builder` like so:
Make sure that Consumer<Example> is under your MultiProvider/Provider<Example>.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
```
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
```
I think the most valid options are 2 :
- The provider you are trying to read is in a different route.
But I don't know because they should be in the same, I mean ShowMultipleAnswers() is the Provider child.
Or the second
- You used a `BuildContext` that is an ancestor of the provider you are trying to read.
In this case, following suggestions above, I should use a builder:(context){} instead of directly calling child : .. but I read that after provider 5 builder has been substituted by create so I'm confused.
If I'm using wrong widgets tell me please!
The error message exactly specifies and describe your error
ShowMultipleAnswers got built using Menu context which doesn't have the Example provider, thus it throws this error.
you can either use builder attribute instead of child or wrap your MaterialApp with the provider
your code should be something like this:
class Menu extends StatefulWidget {
//...SOme code
ChangeNotifierProvider<Example>(
create: (context) => Example(),
builder: (context) {
return ShowMultipleAnswers();
}
//...some code
I forgot to say that there was an other page between Menu and ShowMultipleAnswers, like a bridge between them so the real flow was :
Menu Page -> Bridge Page -> ShowMultipleAnswers Page.
I removed this Bridge Page and it worked! But I still don't understand why it didn't work, maybe because Bridge Page didn't have any references of its Provider?

How reinitialize flutter MultiProvider's all providers data?

In my application, I have many providers, for instance notifications, auth, profile, conversations, trips, etc... And now I need to reinitialize all providers, when user logout. Otherwise old user data will still stay in provider, and new user will get them.
After spending my day, I solved the problem in this way. It is the most elegant way I could do. So after logout, you have to remove all screens and navigate to the root widget, within which your Provider or MultiProvider is created, and so your provider or all your providers inside MultiProvider will be recreated, and all data will be reinitialized
Navigator.of(context).pushAndRemoveUntil<T>(
MaterialPageRoute(
builder: (_) => MyApp(),
),
(_) => false,
);
Where MyApp is the root widget, which is passed as parameter in your main function in main.dart.
runApp(
MyApp(token: token),
);
You can call the providers and clear all the user's data. For example:
You can call your authProvider.logOut(), all still depends on your project structure.
RaisedButton(
child: const Text("Logout"),
onPressed: () {
final Auth auth = Provider.of<Auth>(context, listen: false);
auth.isAuthentificated = false;
},
),

How to create multiple BuildContext inside widget

This is my code
Widget homeDashBoardCards(title, image, BuildContext context) {
return GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DashBoardScreen(),
),
),
);
}
and I need to just be able to change this context to chose the page, instead of creating a lot of Widgets.
homeDashBoardCards('Categories', 'chii-icon.png', context),
like creating 2 cases or more in this one...
builder: (context) => DashBoardScreen
Just as an example:
builder: (context) => case "1" = DashBoardScreen
case "2" = FavoriteScreen
Thank you guys...
1. What I think the Problem Is
What you want is not 100% clear from the question, but I think you simply want a function that would have a switch inside returning which page the user should go to?
In that case, you might want to check out:
Flutter's tutorials on routing
switch statement in Dart
2. One Possible Solution
Create a switch function for deciding which page to switch to. Instead of using Strings, I highly suggest using enums. But both are possible.
enum Screens {dashboard, favorite}
Widget screenSwitcher(Screens screenEnum) {
switch (screenEnum) {
case Screens.dashboard:
return DashBoardScreen();
case Screens.favorite:
return FavoriteScreen();
default:
throw Exception;
}
}
Note that, if you have a non-empty case clause ending with a return, throw or continue, you don't need a break. The default clause doesn't need a break.
Return the screen when routing by calling the switcher function — you can obviously create more parameters for the functions, if necessary —:
Widget homeDashBoardCards(title, image, Screens screenEnum, BuildContext context) {
return GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => screenSwitcher(screenEnum),
),
),
);
}
3. Further Reading
Your code will be much simpler and easier to read if you use Named Routing, as mentioned in the first section of this answer. Additionally, if routing is very important to your app and complicated, you might want to consider looking for or creating a package in the pub.dev package host.
If you accept the solution code I shared above, you're passing variables through different widgets, which is not very object-oriented and also not the Flutter way. You should then consider state sharing packages to work around that. Two of the most notable ones are the BLoC and the Provider packages, but you can also do it with rxdart and the get_it package, check this video by Fireship for a great summary.

Pass context to navigator push to access inherited widget data

I'm trying to access data from an inherited widget placed under the MaterialApp in my tree from a MaterialPageRoute.
When I try to access the data from this route UserModel.of(context).data it say it's null
I get that this is because the UserModel provider should be placed above the whole MaterialApp for it to share the context but it's impossible in my case.
I therefore tried to pass the context from the navigator builder in hope it would give me access to it:
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SettingsScreen(
appContext: context)),
);
}
and then in SettingScreen
UserModel.of(appContext).data
But it didn't work either...
Any idea on how to do this ?
You can just declare a constructor of SettingsScreen and pass your data in constructor. :)