Consumer that checks authentication state breaks when navigating to other screens - flutter

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

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.

How can you listen to user's email being verified?

When a user signs up they are forced to verify their email. This works but I want the user to be automatically led to the next page if they successfully verify their email. I implemented a StreamBuilder with the userChanges() function.
#override
Widget build(BuildContext context){
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.userChanges(),
builder: (BuildContext context, AsyncSnapshot<User?> snapshot){
if(snapshot.data == null){
return const SignupPage();
}else
if(snapshot.data!.emailVerified){
return const HomePage();
}
return const WaitingText();
},
);
}
Now when the user verifies their mail the page doesn't get updated, but when I force a reload with currenUser.reload() it works.
Is a StreamBuilder the way to go here? I don't want to constantly reload my user until they verify.
This sounds like the expected behavior: the userChanges() stream fires an event when any property of the user changes, but there is no immediate propagation of the server-side change to the client.
This means the verification is only picked up in your client when you re-authenticate the user (i.e. sign-out and sign in again) or when you force the SDK to reload the user profile.

Flutter web URL navigation like http://localhost:60117/ItemDetails?pId=1 not working

In my flutter project, i have to share a URL which is directly redirect to a particular product details page through social media . so i have added routing-information using velocity_x, my main.dart page is as follows
#override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primaryColor: MyAppTheme.primaryColor,
accentColor:MyAppTheme.accentColor
),
routeInformationParser: VxInformationParser(),
routerDelegate: VxNavigator(routes: {
"/": (_,__)=> MaterialPage(child:SplashScreen()),
"/login": (_,__)=> MaterialPage(child:SignUp()),
"/home": (_,__)=> MaterialPage(child:Home(selectedIndex: 0)),
"/ItemDetails": (uri, _){
final pId=int.parse(uri.queryParameters["pId"]);
return MaterialPage(
child:ItemDetailsFromAdd(
pId:pId.toString() ,
));
},
}),
)
My Initial route is splash screen and after checking authentication it redirects to login page or home page. when i click on a particular item, creates a url "http://localhost:60117/ItemDetails?pId=1" this. But when i try to launch this url from another tab, first it load product detail page and suddenly redirects to my initial page splashscreen.
I tried to change "/" initial route from "/" to "/splash" and changed my "<base href="/splash/">" but it gives a page not found error initially.
How can i access my product detail page directly using this "http://localhost:60117/ItemDetails?pId=1" URL in correct way.
onGenerateRoute: (settings) {
List<String> pathComponents = settings.name.split('/');
if (pathComponents[1] == 'invoice') {
return MaterialPageRoute(
builder: (context) {
return Invoice(arguments: pathComponents.last);
},
);
} else
return MaterialPageRoute(
builder: (context) {
return LandingPage();
},
);
;
},
Try this way
Try adding a hashtag # to the URL:
http://localhost:60117/#/ItemDetails?pId=1
It's the only way to share it if you are running a progressive web app. What's the left of the /#/ tells the browser from where to get the app, and what's after it is the router logic. If you try to remove the /#/, the app doesn't behave normally and reverts back to the default route.
After long testing i found the answer, here my initial route ('/') redirect to the'SplashScreen'. so when i try to access particular item using URL first it comes to SplashScreen and there i coded to redirect to home or Login based on the user authentication. so when i access to a particular item using url it redirect to my login/home. the solution is i changed my initial route to Home page.

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