Implementation of Firebase Auth with Flutter and Provider - flutter

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.

Related

How do I access the url path params from GoRouter when using a MultiBlocProvider?

Currently we're building an app to learn Flutter and Bloc pattern at my company. We use a MultiRepositoryProvider as the main widget and GoRouter for routing. My route looks like this:
GoRoute(
path: '/game/:id',
builder: (context, state) => GameDetailScreen(),
),
In the MultiRepositoryProvider the child is a MultiBlocProvider and the provider for this screen is:
BlocProvider(
create: (BuildContext context) {
return GameDetailBloc(context.read<FirestoreRepo>());
},
),
The BlocProvider's create function returns the BuildContext but it's not clear to me how I get the GoRoute state to pass the url param id to the GameDetailBloc.
We managed to get this to work by setting the game's id in GoRoute's build function when creating the GameDetailScreen. Then we removed that BlocProvider in the MultiBlocProvider and then accessed the bloc from the BuildContext when building the widget but it doesn't seem correct and we're trying to find the "correct solution" to this problem. Any help is greatly appreciated. Thanks!
go_router has it's params in its state.
Hence pass the state to the page
Router
GoRoute(
name: "test",
path: "/test/:id",
builder: (context, state) {
return SampleWidget(
goRouterState: state, 👈 Pass state here
);
},
),
Usage
context.goNamed("test", params: {"id": "123"}),
Accesing in the page
class SampleWidget extends StatelessWidget {
GoRouterState? goRouterState;
SampleWidget({super.key, this.goRouterState});
#override
Widget build(BuildContext context) {
print(goRouterState?.params.toString()); 👈 access anywhere like so
return const Scaffold(
body: ...
);
}
}
I couln't completely understand the question. I have attempted to answer based on my interpretation. Let me know the specifics if you have any other requrirements.
Uri.base.toString().replaceAll(Uri.base.origin, '')

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

Provider in different route 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>()...

Provider not accesable when Navigate to new screen

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
// ...
}

Binding object and widget together in Flutter using Provider package

Imagine in the TodoScreen of the application, I have a TodoObjectList (list of todos obtained from some API) and I want to show them inside a list. So I created a list of TodoWidgets (StatelessWidget), each one having its own TodoObject as its property. Now I want TodoWidgets to be bound with its TodoObject, so I used the Provider package. The code is something like below (inside TodoScreen):
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ChangeNotifierProvider<TodoObject>(
builder: (_) => TodoObjectList[index],
child: Consumer<TodoObject>(
builder: (context, TodoObject, child) => TodoWidget(todo: TodoObject)
),
);
},
childCount: TodoObjectList.length,
),
)
This code works fine for the first time. But when I go back and navigate to TodoScreen for the second time (I won’t call the API again, TodoObjectList is already cached), Provider throws an error:
“A TodoObject was used after being disposed.”
I Know why this error is being thrown, but my question is how should I bind TodoWidget with TodoObject using provider, without facing this error when I have the API data stored somewhere.
You should use ChangeNotifierProvider.value instead. This link would probably help you if you had any follow up questions.