How to keep user signed in Flutter Firebase app? - android

I am creating a chat app that uses Firebase for the backend.
Whenever I restart the app, it goes to the welcome screen where users have to login/register, every single time.
If you log in, then close the app without logging out, it still goes back to the login screen. I have added a method in the main method to check:
void getCurrentUser() async {
FirebaseAuth.instance.onAuthStateChanged.listen((firebaseUser) {
print(firebaseUser);
_user = firebaseUser;
});
}
So, if _user is null, then go to welcome page, otherwise go straight into the chat screen of the app:
Widget build(BuildContext context) {
getCurrentUser();
return MaterialApp(
initialRoute: _user == null ? '/' : '/chat',
routes: {
'/': (context) => WelcomeScreen(),
'/login': (context) => LoginScreen(),
'/registration': (context) => RegistrationScreen(),
'/chat': (context) => ChatScreen(),
},
);
}
Why doesn't it work? I have Googled this problem for a while and earlier I was using auth.currentUser(), which could have been the problem, but now I'm using a listener and it still doesn't work.

var user await FirebaseAuth.instance.currentUser();
if (user != null) {
Navigator.of(context).pushNamed("routeName");
}
Wrap the above code in an async function that takes BuildContext and call in the build method before the return statement.

Related

How do I fix "Do not use BuildContexts accros async gaps" when using Snack Bar and Navigator in flutter

This is my code
_signinRouter(
userId,
BuildContext context, {
phone = false,
email = false,
googleSignUp = false,
userData,
}) async {
NetworkController network = NetworkController();
setState(() {
_isLoading = true;
});
CallResponse response = await network.fetchUserData(
userId,
phone: phone,
email: email,
);
setState(() {
_isLoading = false;
});
if (response.status == 1) {
debugPrint(response.msg.toString());
//IF USER DATA IS FOUND
showSnackBar('User account exists', context);
} else {
//Since network returns one or zero, 1 for the success of request and 0 for both server error and failure of request
//Response msg will return null if user was not found and it will return an actual server failure msg if it was a server error
if (response.msg == 'null') {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => (googleSignUp)
? GooglePostSignUp(userData: userData)
: ServerSignUp(phoneNumber: userId, userData: userData),
),
);
} else {
showSnackBar(response.msg, context);
}
}
}
}
I am calling the function within a stateful widget and I defined a function to help in calling the snack bar, my app relies heavily on snack bars and I have this same error all across my files. This is the snack bar function
void showSnackBar(String? value, BuildContext context) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
value!,
style: p2(
false,
),
),
duration: const Duration(seconds: 2),
action: SnackBarAction(
label: 'Close',
textColor: themeColor,
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
),
),
);
}
I can call the my custom snack bar from anywhere with in my project easily.
I can trick the IDE and lint by removing the Buildcontext type in the function but it not practical, i can check if the widget is mounted before calling the snackbar but i dont think it is very practical ???
I know you are thinking of app performance in mind, Oh! wait! flutter already did that for you :). You don't need to work around it except if you have time and energy. Note that, the context has to be passed every time and so checking the context mount every time is not "i don't think it is very practical" situation
Things like this I call (The warning error). When working with async functions within a context that is likely to be lost due to nesting. You need to handle it with care.
One way to do that is by checking for the widget-mounted state. basically, Don't use context after async if you're not sure your widget is mounted.
To be sure use the following:
if (!mounted) return; // check before calling `Navigator`
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => (googleSignUp)
? GooglePostSignUp(userData: userData)
: ServerSignUp(phoneNumber: userId, userData: userData),
),
);
To make the code short-hand just do the normals!!! :) break the code down in the same file!!
Bye.

Flutter WillPopScope works normally for me but when I navigate or push back to the same page it does not work

My problem is that I have a main page then sign in page then a homepage where if I press logout in the homepage it should navigate me back to the main page which contains the willpopscope , what I'm trying to do is to prevent the user from pressing the back button in the main page so that the app does not return to the homepage without the user being signed in , the problem is that the willpopscope does not work when I Navigator.push the homepage , so whenever I press the back button it returns me to the home page.
I tried changing the position of WillPopScope, If I wrap the whole widget by it , it will never work for me
Code to reproduce
//Main page:
class MainActivity extends StatefulWidget {
final bool isWelcome;
MainActivity(this.isWelcome);
#override
State<StatefulWidget> createState() {
return OperateMainActivity(isWelcome);
}
}
class OperateMainActivity extends State<MainActivity> {
bool isWelcome = false;
OperateMainActivity(this.isWelcome);
#override
Widget build(BuildContext context) {
//print(isWelcome);
return MaterialApp(
home: MyHome(
isWelcome: isWelcome,
));
}
}
class myHome extends StatelessWidget
return Scaffold(
body: Center(
child: WillPopScope(
onWillPop: () async => false,
child: Container(....)
// Home page
Navigator.push(context,MaterialPageRoute(builder: (context) => MainActivity(false)));
//When pushing to main activity WillPopScope does not work
WillPopScope doesn't get called when you push. That's the logical behavior, because when you're pushing another view it goes above the current view in the stack. Therefore, the current view never pops out.
If you know that your MainActivity is below you're current page in the stack, you could change:
Navigator.push(context,MaterialPageRoute(builder: (context) => MainActivity(false)));
to:
Navigator.pop(context); // This will make a call to onWillPop.
UPDATE:
For what you want to achieve, you could just use pushAndRemoveUntil:
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) {
return MainActivity();
},
),
(Route<dynamic> route) => false,
);
It will push the desired page and remove everything else from the stack.

How to remove widget from initstate or load widget widget in initstate only once?

Apologies if my question is a bit dumb but I have just started with flutter.
I am working on sharing and deep link flutter. I have created intent and everything is working perfect except one. I am calling "initUniLinks" on my homepage not mainpage.
So link and navigation is working perfect but when I open app through link shared and specific page opens but after that when go to homepage it again gets the "initUniLinks" and again redirects to specified page.
I am using Uni-link plugin and app or deep link method from this
https://pub.dev/packages/uni_links
I have tried not too much but some basic options like giving null value to initialLink before navigation but as my widget is in initstate it is called again when I come back to homepage.
class HomePage extends StatefulWidget {
final Widget child;
HomePage({Key key, this.child}) : super(key: key);
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
String initialLink = "";
void initState() {
super.initState();
initUniLinks();
}
Future<Null> initUniLinks() async {
try {
initialLink = await getInitialLink();
if (initialLink != null || initialLink != "") {
initialLink.contains("pgpost")
? await Navigator.push(
context, MaterialPageRoute(builder: (context) => PostPage()))
: await Navigator.push(context,
MaterialPageRoute(builder: (context) => UserPage()));
}
} on PlatformException {
}
}
}
I just want to know is there any way that I can call 'initUniLinks" just one time not every time by removing from initstate ?
Or any other better solution.
Scenario here is simple I want my user to navigate to deep link page only one time when he clicks the link but after that my app should be able to navigate normally.
I guess the problem is you calling navigator from initState, the widget tree is not complete when you navigate to another page, so when you come back to home page the initState is called again. You can use this sheet:
WidgetsBinding.instance.addPostFrameCallback((_) => initUniLinks()); in your initState, that will call a function when the widget is fully complete.
Please give me the feedback if its work
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => initUniLinks());
}

close app on device back button in flutter

The question may be duplicate but I'm unable to found the solution. My app scenario is the same as the almost app. My first screen is Splash screen and holds for two seconds and here login session is checked and upon this condition screen changes like this and below code is run in initState()
_checkPreference() async {
PreferencesConnector myprefs= PreferencesConnector();
id=await myprefs.readString('merchantid');
if(id==null||id==''){
Future.delayed(
const Duration(seconds: 2),
() => Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => Login(),settings: RouteSettings(name: '/login')),
));
}else{
Future.delayed(
const Duration(seconds: 2),
() => Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => DashBoard()),
));
}
}
If the session returns false then it goes to login screen, In Login screen my scaffold inside in WillPopScope widget and that class is stateful class
return WillPopScope(
onWillPop: () {
if (Navigator.canPop(context)) {
//Navigator.pop(context);
Navigator.of(context).pop();
} else {
SystemNavigator.pop();
}
},
child:Scaffold(
body: Stack(
children: <Widget>[
and if LoginApi returns true then it move to dashboard like this
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => DashBoard(),
settings: RouteSettings(name: '/dashboard')),
);
here everything is working fine when the user is already logged-in and we reach to dashboard after the splash and but there is a logOut button on my dashboard when user press logout then there is a dialog appear which asks for logout- if i press yes from dialog button works like this
onPressed:(){
clearSession();
// Navigator.of(context).popUntil(ModalRoute.());
// Navigator.of(context).popUntil('/login', (Route<dynamic> route) => false);
// Navigator.popUntil(context, ModalRoute.withName("/login"));
// Navigator.of(context).pushNamedAndRemoveUntil('/login', (Route<dynamic> route) => false);
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => Login()),);
},
after pressing logout it reaches to login screen where I'm unable to close the app when user press back from the device back in login screen but it redirects to the dashboard and then I pressed back then app closed.
What you need to do is first clear all path before going to login() screen.
try this:
onPressed:(){
clearSession();
//Navigator.popUntil(context, ModalRoute.withName('/'));
Navigator.pop(context,true);// It worked for me instead of above line
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => Login()),);
},
call this on your logout button
void _logout() {
Navigator.popUntil(context, ModalRoute.withName('/login'));
}
here is the link of official docs

Flutter - How to have A Dynamic Home Page by using Firebase?

I am new to Flutter and Firebase and am working on an authentication app.
How do I make the app go to the HomePage if a user is signed in already or go to the login page if no user is signed in?
Forgive my rusty code.
FirebaseAuth auth = Firebase.instance;
void main() {
Future<bool> home() async {
if (await auth.currentUser() == null) {
return false;
}
return true;
}
runApp(
new MaterialApp(
title: 'DASH',
home: home() == null ? LoginScreen() : HomeScreen(),
theme: DashTheme.theme,
color: DashTheme.white,
routes: <String, WidgetBuilder>{
// Set routes for using the Navigator.
'/home': (BuildContext context) => new HomeScreen(),
'/login': (BuildContext context) => new LoginScreen(),
},
),
);
}
I'm using Future Builder
Widget _getLandingPage() {
return FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot){
if (GlobalStore.state.userConfig != null && GlobalStore.state.userConfig.isDisplayIntroScreen == true) {
return routes.buildPage('intro_screen', null);
}
if (snapshot.hasData) {
return routes.buildPage('main', null);
} else {
return routes.buildPage('auth', null);
}
},);
home: _getLandingPage(),
While I never wrote anything in the Dart language, I can see that you wrote some good beginning. If we take a look at this line:
home: home() == null ? LoginScreen() : HomeScreen(),
You are checking in home() if the user is logged in. However, you are checking if the return value is null while the returned object of the method is Future<bool>. (This means that in this case the condition is always false).
Also note that this method is async. That means that you will not really know when this method is actually done executing. (This will be some moment in the future).
That being said, you should display an initial page once the app starts up. Then you should run and check the return value of the future method. According to the docs (https://www.dartlang.org/tutorials/language/futures) this can be done like this:
// Call this after the initialization of your app and pages
final future = home();
future.then((isLoggedIn) => ); // Inside here you need to do a navigation to the homepage or loginpage
A little modification is required in your code.
Instead of checking the current user in the main function like that, what you can do is check user in the initState() function.
#override
void initState()
{
super.initState();
final FirebaseAuth _auth = FirebaseAuth.instance;
if(_auth.currentUser() == null)
{
Navigator.of(context).pushNamedAndRemoveUntil("/LoginScreen" , (Route<dynamic> route) => false);
}
else
{
Navigator.of(context).pushNamedAndRemoveUntil("/HomeScreen" , (Route<dynamic> route) => false);
}
}
pushNamedAndRemoveUntil is used if you don't want the user to go back to the previous scene or route.
You can Read about Navigation in flutter here. And use the one you need.
Thanks alot for all your answers.
I was checking up on the answers and this is what I came across.
I don't know if it's the right thing to do but it is actually working.
Here is the code:
Widget _getLandingPage() {
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.providerData.length == 1) {
return snapshot.data.isEmailVerified ? HomeScreen() : VerifyEmailScreen();
} else {
return HomeScreen();
}
} else {
return LoginScreen();
}
},
);
}
home: _getLandingPage(),

Categories

Resources