How reinitialize flutter MultiProvider's all providers data? - flutter

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;
},
),

Related

Rebuild app or delete cache when redirect to home page Flutter

I have an app where when I logout with Firebase Auth, user's datas are still there, and I need to Hot restart the app to totally remove them.
I didn’t find a way to improve the signout method, so I want to try to rebuild app when user logout, or maybe delete the cache so user’s datas will no longer exist in app.
I have done some research but I can’t find any solution that can help me.
EDIT : Here's how I logout
Future<void> signOut() async {
await FirebaseAuth.instance
.signOut()
.then((value) => print('Sign Out'))
.onError((error, stackTrace) => print('Error in signed out'));
}
IconButton(
onPressed: () async {
await signOut();
Navigator.of(context, rootNavigator: true)
.pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
return const OnBoardingPage();
},
),
(_) => false,
);
},
icon: const Icon(Icons.logout))
I know that user's data are still there because I can display user.email in my onBoardingPage (where technically no data can be there because I logout previously).
Here a preview of my onBoardingPage :
After that, if I want to connect with another account, I will be connected to the previous user's session. The same if I want to create a new account, all new user's data will be into the previous connected user. To fix this problem, in development mode, I need to hot restart the app.
It seems like the user is not totally logout.

Consumer that checks authentication state breaks when navigating to other screens

Users of my app need to authenticate. The authentication state is tracked by a provider class. The login functionality works fine, however, users can also logout, and that is giving problems.
In main.dart I use a consumer widget to track the authentication state:
child: Consumer<AuthenticationProvider>(
builder: (ctx, auth, child) => MaterialApp(
title: 'FlutterChat',
home: auth.isAuth
? WelcomeScreen()
: FutureBuilder(
future: auth.tryAutoLogin(),
builder: (ctx, authResultSnapshot) =>
authResultSnapshot.connectionState ==
ConnectionState.waiting
? SplashScreen()
: AuthFlowScreen(),
),
),
),
At app start, the consumer will check the authentication state, if the user is not authenticated, then the user will be redirected to AuthFlowScreen, else the user will see the WelcomeScreen. If the user followed the AuthFlowScreen and successfully authenticates, the user will automatically go to the WelcomeScreen, because that is what the Consumer does.
The WelcomeScreen has a NavBar from which the user can navigate to other parts of the app. The NavBar also has a button to Logout. When this is pressed, the consumer in the main.dart file needs to react. As long as the user didn't navigate to any other screen, the logout works and the AuthFlowScreen is shown to the user. However, if the user navigated to other parts in the app, this does not happen.
Navigation happens with:
Navigator.push(context, MaterialPageRoute(builder: (context) {
return ChatOverviewScreen();
}))
Logout script:
void logout() {
_auth.signOut();
_userEmail = null;
_userId = null;
notifyListeners();
}
It seems that the navigation is messing up the consumer in the main.dart file, however, how can I navigate through the app, such that the consumer in main.dart is still working.
for login system better to use bloc state management, you can find full example here :
https://bloclibrary.dev/#/flutterlogintutorial

How to combine multiple providers in Flutter

I am new to Flutter development.
I am building application where once users login they are shown list of posts.
If the user is not login still they are shown some random post.
I got parts of the application various posts in the internet.
This is what I did
class TestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<AuthBase>(
create: (context) => Auth(),
),
StreamProvider<User>.value(
value: Auth().onAuthStateChanged,
),
ProxyProvider<User, Database>(
update: (BuildContext context, User user, Database db) {
return user == null
? FirestoreDatabase(uid: null)
: FirestoreDatabase(uid: user.uid);
}),
ChangeNotifierProxyProvider<Database, PostProvider>(
create: (context) => PostProvider(),
update: (ctx, database, previousProvider) {
print("at ChangeNotifierProxyProvider $previousProvider");
return previousProvider.update(database);
},
),
],
child: MaterialApp(
title: 'Test Project',
home: LandingPage(),
),
);
}
My idea is;
if the user is logged in StreamProvider<User>.value will give that user to
ProxyProvider<User, Database> and it will create database with that user.
And that database in turn will be used by ChangeNotifierProxyProvider<Database, PostProvider> to create the provider that actually get posts. It uses the database to get the posts.
I noticed even if the user is logged in at the start I get a null value for the user then immediately i get the actual user.
In the landing page, I only have the following line and it generate an error
final provider = Provider.of<PostProvider>(context);
I noticed the cause for the error was this line;
previousProvider.update(database);
First time time Postprovider constructor and update methods get called without any problem. (here we get null user)
When the FirestoreDatabase get created with the actual user "previousProvider" is null.
This is the reason for the error.
Not sure why this is happening or how to fix it.
Is there a better way of doing this?
Initially getting a null value even when there is a logged in user may be the reason. How to prevent it?
I will try to explain your code a bit so we are on the same page
Provider<AuthBase>(
create: (context) => Auth(), //this subscribe an Auth Class to the provider
),
StreamProvider<User>.value(
value: Auth().onAuthStateChanged,
/// This subscribe a stream from another Auth Class instance
/// Unless Auth its a singleton class, this is not related to the previous Provider
/// and changes to Provider<AuthBase> won't affect this Stream
),
I noticed even if the user is logged in at the start I get a null
value for the user then immediately i get the actual user.
That happens when using Stream, while the first value is captured (sometimes is delayed a tick) its null, you can use initialData parameter if you know the first value while the stream subscription is done
Finally
ChangeNotifierProxyProvider<Database, PostProvider>(
create: (context) => PostProvider(),
update: (ctx, database, previousProvider) {
print("at ChangeNotifierProxyProvider $previousProvider");
return previousProvider.update(database);
},
),
update expects to return a value of type PostProvider, but you're returning whatever the method previousProvider.update(database) returns (which I think it just updates some inner variable or something, I believe it's some void method maybe?)
Change the update like this
update: (ctx, database, previousProvider) {
print("at ChangeNotifierProxyProvider $previousProvider");
return previousProvider..update(database);
/// It's the same as writing this
/// previousProvider..update(database);
/// return previousProvider;
},

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

How to scope a ChangeNotifier to some routes using Provider?

I have a ChangeNotifier, and I would like to share it between multiple routes but not all routes:
Page1 is my first page. I need share data of ChangeNotifierProvider with Page2, Page3 and Page only and on enter Page1 call dispose of my ChangeNotifierProvider.
How can I do this using provider?
To do so, the easiest solution is to have one provider per route, such that instead of:
Provider(
builder: (_) => SomeValue(),
child: MaterialApp(),
)
you have:
final value = SomeValue();
MaterialApp(
routes: {
'/foo': (_) => Provider.value(value: value, child: Foo()),
'/bar': (_) => Provider.value(value: value, child: Bar()),
'/cannot-access-provider': (_) => CannotAccessProvider(),
}
)
It is, on the other hand, not possible to have your model "automatically disposed".
provider is not able in such a situation to know that it is safe to dispose of the object.