Provider in different route Flutter - flutter

I wanted to upload a picture inside Firebase Storage when I saw this error :
The provider you are trying to read is in a different route.
So I started study what is this Provider and now I understood that I need to include the page that I'm using to upload the picture inside the same route of the ancestor Provider of type Database.
So I had the issue inside "Registration_form" that is created by "LogInPage" through a Navigator like this :
void navigateToRegistrationForm(context) {
Navigator.push(context,
MaterialPageRoute<void>(builder: (BuildContext context) {
return Reg_Form();
}));
return;
}
Now I'm changing the code that is something like :
void navigateToRegistrationForm(context) {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) => Provider<Database>(
create: (_) => FirestoreDatabase(),
builder: (context, child) => Reg_Form(),
)));
return;
}
But I don't understand what I should pass to the create method...I just want to create Reg_Formin the right route to call Firebase functions to put the file inside firebase storage so I think that FirebaseDatabase isn't what suits my needs.

If I understand your issue correctly, you can either move your ChangeNotifierProvider for your Database class higher up in the widget tree by wrapping your MaterialApp like so:
ChangeNotifierProvider<Database> (
builder: (context) => Database(),
child: MaterialApp(...
Or just change the create in what you provided to
create: (_) => Database(),
Either way should give you access to the correct Provider/BuildContext in your Registration form with standard context.read<Database>()...

Related

Why it's useless trying to pass 'context' to showDialog?

I'm using the plugin 'Provider' to get some information and when I'm trying to show a dialog and get a info from my Provider, something weird happened.
The code seems like this:
showDialog(
context: context,
builder: (context) {
return LoadingDialog(); //LoadingDialog is one of my own class ,which extends AlertDialog.
}
);
but it throws a error like:
ProviderNotFoundException (Error: Could not find the correct Provider<LoginState> above this LoadingDialog Widget
So I set a breakpoint and checked the context in the class LoadingDialog, but it's like:
StatelessElement(LoadingDialog(dirty))
So i think the context from the outside page failed to pass into this dialog. So later I read the Flutter API doc and it says:
This function takes a builder which typically builds a Dialog widget. Content below the dialog is dimmed with a ModalBarrier. The widget returned by the builder does not share a context with the location that showDialog is originally called from. Use a StatefulBuilder or a custom StatefulWidget if the dialog needs to update dynamically.
So i tried to use the StatefulBuilder,the code seems like this:
showDialog(
context: context,
builder: (context) {
bool tempLoginState = Provider.of<LoginState>(context, listen: true).getState;//my own method returns true.
return AlertDialog(
content: StatefulBuilder(
builder: (context, setState){
//my dialog detail codes
}
)
)
}
);
But actually, the first line in the builder:
bool tempLoginState = Provider.of<LoginState>(context, listen: true).getState;
it doesn't work, because the context here is also the same as
StatelessElement(LoadingDialog(dirty))
So how can I pass the context to the dialog on earth? Thank you for your ansering.

StreamProvider returns no data when used with Navigator

The issue is that I don't get any values out of my StreamProviders (which are defined on a global level) within my Authenticated route:
runApp(MultiProvider(
providers: [
Provider.value(value: userService),
StreamProvider.value(value: authService.userStream, initialData: null),
StreamProvider.value(value: userService.userDataStream),
StreamProvider.value(value: userService.characterStream),
],
child: MyApp(),
));
}
I noticed that it's to do with the logic that I have for my Navigator (if I remove it the provider values are passed down the widget tree as expected). The Navigator I'm using is based around the idea that the app has 3 states: Not Authenticated, Authenticated and Authenticated-First-Time. I get the value whether I'm authenticated from the loginStream (so far everything works):
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: loginStream,
builder: (ctx, snapshot) {
if (!snapshot.hasData) return Loading();
LoginState state = snapshot.data;
if (state == LoginState.LOGGED_OUT) return LoginScreen();
if (state == LoginState.FIRST_TIME) return CharacterCreationScreen();
return Navigator(
key: navigatorKey,
initialRoute: "/home",
onGenerateRoute: (settings) => PageRouteBuilder(
pageBuilder: (ctx, _, __) => routes(settings)(ctx),
transitionsBuilder: pageTransition,
),
);
},
);
The thing is that if I'm Authenticated and say in the HomeScreen, then both userDataStream and characterStream return null even if there's actual data available. If I remove the StreamBuilder + LoginLogic itself and just have the Navigator widget returned above, then HomeScreen gets the correct values.
UPDATE:
I noticed that it's not even the StreamBuilder. If I remove the 3 if's within the builder, then the stream values are propagated correctly. Not sure why that happens.
I´m not quite sure if this helps since I´m lacking details but here is what I noticed so far:
If you create the objects in the multiprovider for the first time you should not use .value - check if this applies.
Try cleaning up the if statements in the function body of your StreamBuilder (use if, else if and else keywords.
Also, following your description, it sounds like whenever an if statement is true, returns and thus cancels the build´s function body, the stream somehow resets and defaults to null. Maybe look into that & update your question.
Change this
Provider.value(value: userService),
StreamProvider.value(value: authService.userStream, initialData: null),
To this
Provider(create: (context) => userService)
StreamProvider(create:(context) => authService.userStream, initialData: null),
Do the same for all the providers that u are registering
To expose a newly created object, use the default constructor of a provider. Do not use the .value constructor if you want to create an object, or you may otherwise have undesired side effects.
https://pub.dev/packages/provider

Implementation of Firebase Auth with Flutter and Provider

I am a bit confused about the implementation of Provider with Firebase Auth. Every tutorial seems to use different methods. I have a service named AuthService which contains a variable final FirebaseAuth auth = FirebaseAuth.instance. So, I want to use the following stream: AuthService.auth.onAuthStateChanged in a widged. The question is should I use something like:
StreamProvider<FirebaseUser>.value(
value: AuthService().auth.onAuthStateChanged,
child: ...
or create a constructor like this:
Provider<AuthService>(
create: (_) => AuthService(),
child: ...
)
and access it somewhere in the widget:
final authService = Provider.of<AuthService>(context);<br>
final user = authService.auth.onAuthStateChanged;
You can use similar to this thing, If user is logged in he will lead to MainScreen() otherwise it will lead you to LoginPage(), you don't need to use
provider
Widget _handleAuth() {
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
return (!snapshot.hasData)
? LoginPage()
: MainScreen();
},
);
The provider package exposes different types of providers, you can check all types in their page here (At the bottom of their page).
Using a provider instead of another it depends on your needs, as far I'm concerned the Provider(the first one in the list) is fine.

Why does screen that gets arguments from ModalRoute re-build on Navigator pop?

I could not understand why my screen build was being executed when popping. I narrowed it down to this line:
final String userId = ModalRoute.of(context).settings.arguments;
Why does the screen rebuild? How should I stop it rebuilding, or bail out when it happens?
Here is a complete example, with counter-example:
https://dartpad.dev/9f83473a923e39e9c4b07840bc4aded7
The problem seems to be know by now, but no actual fixes have actually been found somehow (or so it seems).
It is discussed here : https://github.com/flutter/flutter/issues/63364
As well as here : https://github.com/flutter/flutter/issues/35575
Generally, the accepted solutions seem to either :
Pass the arguments to the page directly through parameters of the page class (see 'vivek yadav' answers).
Retrieve the arguments in the initState() method and directly inside addPostFrameCallback() (see code below) rather than in the build() or didChangeDependencies() methods.
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final args = ModalRoute.of(context).settings.arguments;
print(args);
});
}
However, I am still very interested to know if you found another solution or actual fix to this issue. Currently, I am using the 1st solution I listed above but I am not quite satisfied with it to be honest.
We pass a class object in MaterialPageRoute for eg
Navigator.of(context).push(
MaterialPageRoute(
builder: (ctx) => ScreenWithoutArg(),
),
);
Sometimes, we need to send data while routing, also in some cases class is selected dynamically.
In such a scenario, If we add data implementation in class constructor then it's not a good coding practice.
Rather than we can pass the same data in RouteSettings.
// suggested
Navigator.of(context).push(
MaterialPageRoute(
builder: (ctx) => ScreenWithArg(),
settings: RouteSettings(arguments: 'Arg'),
),
);
// not suggested (Prone to code changes)
Navigator.of(context).push(
MaterialPageRoute(
builder: (ctx) => ScreenWithArg(arg1: 'argdata',arg2:'agrdata2'),
settings: RouteSettings(arguments: 'Arg'),
),
);
Thanks, #Patrick O'Hara for highlighting this stunning feature of the dart.
I dont get complatly your question but if you want to delete or pop before navigator
Navigator.of(context).push(
MaterialPageRoute(
builder: (ctx) => ScreenWithArg(),
settings: RouteSettings(arguments: 'Arg'),
),
);
instead use this line code
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (ctx) => ScreenWithArg(),
settings: RouteSettings(arguments: 'Arg'),
),
);

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.