'How to check if the user is logged in, if so show other screen?

My first screen is a login screen and it needs to check if the user is logged in to open the home screen directly but I get an error using this check.

I'm doing the check on initState, the condition is returning true, so looks like the problem is with the Navigator.

What is the correct way to skip the first screen if the user is logged in?

Error:

I/flutter (20803): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (20803): The following assertion was thrown building Navigator-[GlobalObjectKey<NavigatorState>
I/flutter (20803): _WidgetsAppState#8ce27](dirty, state: NavigatorState#db484(tickers: tracking 2 tickers)):
I/flutter (20803): 'package:flutter/src/widgets/navigator.dart': Failed assertion: line 2106 pos 12: '!_debugLocked':
I/flutter (20803): is not true.
I/flutter (20803): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter (20803): more information in this error message to help you determine and fix the underlying cause.

Code:

class LoginScreen extends StatefulWidget {
  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {


  final _emailController = TextEditingController();
  final _passController = TextEditingController();

  final _formKey = GlobalKey<FormState>();
  final _scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  void initState() {
    super.initState();

    if(FirebaseAuth.instance.currentUser() != null){

      Navigator.of(context).pushReplacement(MaterialPageRoute(
        builder: (context) => HomeScreen()
      ));
    }

  }


  @override
  Widget build(BuildContext context) {

    return Scaffold(
      key: _scaffoldKey,
      body: ScopedModelDescendant<UserModel>(
          builder: (context, child, model){

            if(model.isLoading)
              return Center(
                child: CircularProgressIndicator(),
              );

            return Form(
              key: _formKey,
              child: ListView(
                padding: EdgeInsets.all(16),
                children: <Widget>[
                  SizedBox(height: 67),
                  Icon(Icons.chrome_reader_mode, size: 150, color: Colors.blue,),
                  SizedBox(height: 16,),
                  TextFormField(
                    controller: _emailController,
                    decoration: InputDecoration(
                        hintText: "Digite seu e-mail",
                        border: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(10),
                        ),
                      fillColor: Colors.blueAccent
                    ),
                    keyboardType: TextInputType.emailAddress,
                    validator: (text){
                      if(text.isEmpty || !text.contains("@"))
                        return "E-mail inválido!";
                    },
                  ),
                  SizedBox(height: 16,),
                  TextFormField(
                    controller: _passController,
                    decoration: InputDecoration(
                        hintText: "Digite sua senha",
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(10),
                        ),
                        fillColor: Colors.blueAccent
                    ),
                    obscureText: true,
                    validator: (text){
                      if(text.isEmpty || text.length < 6)
                        return "Digite a senha!";
                    },
                  ),
                  SizedBox(height: 16,),
                  FlatButton(
                    padding: EdgeInsets.all(13),
                    shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(10)
                    ),
                    color: Colors.blue,
                      child: Text("Entrar",
                        style: TextStyle(
                          color: Colors.white,
                          fontSize: 20
                        ),
                      ),
                      onPressed: (){

                          if(_formKey.currentState.validate()){

                            model.signIn(

                              email: _emailController.text,
                              pass: _passController.text,
                              onSuccess: _onSuccess,
                              onFail: _onFail,

                            );

                          }


                      },
                  ),
                  SizedBox(height: 10,),
                  InkWell(
                    onTap: (){

                      if(_emailController.text.isEmpty || !_emailController.text.contains("@")){

                        _scaffoldKey.currentState.showSnackBar(
                            SnackBar(content: Text("Insira um e-mail válido para recuperação",
                              style: TextStyle(fontSize: 14),
                            ),
                              backgroundColor: Colors.redAccent,
                              duration: Duration(seconds: 3),
                            )
                        );

                      } else {
                         model.recoverPass(_emailController.text);
                         _scaffoldKey.currentState.showSnackBar(
                           SnackBar(
                             content: Text("O e-mail de recuperação foi enviado!",
                               style: TextStyle(fontSize: 14),
                             ),
                             backgroundColor: Colors.green,
                             duration: Duration(seconds: 3),
                           )
                         );
                      }

                    },
                    child: Text("Esqueci minha senha",
                      style: TextStyle(
                          color: Colors.black,
                          fontSize: 16,
                          fontWeight: FontWeight.w400
                      ),
                      textAlign: TextAlign.center,

                    ),
                  ),
                  SizedBox(height: 30,),
                  InkWell(
                    onTap: (){

                      Navigator.of(context).push(MaterialPageRoute(
                          builder: (context)=> SignUpScreen())
                      );

                    },
                    child: Text("Não tem conta? Cadastre-se!",
                      style: TextStyle(
                        color: Colors.black,
                        fontSize: 16,
                        fontWeight: FontWeight.w600
                      ),
                      textAlign: TextAlign.center,

                    ),
                  ),
                ],
              ),
            );

          },
      ),


    );
  }




}


Solution 1:[1]

Well you can solve this kind of problem using another approach. Instead check if there is user logged inside your loginScreen class you can do this a step before and then decide if you will show the loginScreen if there is no user logged or show another screen, MainScreen I' am supposing, if the user is already logged.

I will put some snipet showing how to accomplish this. I hope it helps. But before I will explain you what is wrong in your source code.

if(FirebaseAuth.instance.currentUser() != null){
      // wrong call in wrong place!
      Navigator.of(context).pushReplacement(MaterialPageRoute(
        builder: (context) => HomeScreen()
      ));
}

Your code is broken because currentUser() is a async function and when you make the call this function is returning a incomplete Future object which is a non null object. So the navigator pushReplacement is always been called and it's crashing because the state of your widget is not ready yet.

Well as solution you can user FutureBuilder and decide which screen you will open.

int main(){
   runApp(  YourApp() )
}

class YourApp extends StatelessWidget{

    @override
    Widget build(BuildContext context){
        return FutureBuilder<FirebaseUser>(
            future: FirebaseAuth.instance.currentUser(),
            builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot){
                       if (snapshot.hasData){
                           FirebaseUser user = snapshot.data; // this is your user instance
                           /// is because there is user already logged
                           return MainScreen();
                        }
                         /// other way there is no user logged.
                         return LoginScreen();
             }
          );
    }
}

Using this approach you avoid your LoginScreen class to verify if there is a user logged!

As advise you can make use of snapshot.connectionState property with a switch case to implement a more refined control.

Solution 2:[2]

Achieved without Firebase, but by using SharedPreferences

find code at gist

here is a simple code: Main.dart

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final SharedPreferences prefs = await SharedPreferences.getInstance();
  var isLoggedIn = (prefs.getBool('isLoggedIn') == null) ? false : prefs.getBool('isLoggedIn');
  runApp(MaterialApp(
    debugShowCheckedModeBanner: false,
    home: isLoggedIn ? anotherPage() : loginPage(),
  ));
}

using flutter package: shared_preferences

DemoApp if you don't want to use SharedPreferences in Main()

Solution 3:[3]

Make use of FirebaseAuth to get the current user. If the user is not logged in the value is null otherwise, the user is logged in.

// Get the firebase user
User firebaseUser = FirebaseAuth.instance.currentUser;
// Define a widget
Widget firstWidget;

// Assign widget based on availability of currentUser
if (firebaseUser != null) {
  firstWidget = Home();
} else {
  firstWidget = LoginScreen();
}

// Run the app with appropriate screen
return MaterialApp(
  debugShowCheckedModeBanner: false,
  title: 'UniClass',
  theme: ThemeData(
    primaryColor: kPrimaryColor,
    scaffoldBackgroundColor: Colors.white,
  ),
  home: firstWidget,
);

Solution 4:[4]

I did it using Shared preferences in main.dart file. Worked fine for me.

Widget build(BuildContext context) {
return FutureBuilder(
    future: SharedPreferences.getInstance(),
    builder:
        (BuildContext context, AsyncSnapshot<SharedPreferences> prefs) {
      var x = prefs.data;
      if (prefs.hasData) {
        if (x.getBool('isloggedin')) {
          if (x.getString('type') == 'doctor') {
            return MaterialApp(home: DrHome());
          } else
            return MaterialApp(home: PtHome());
        }
      }

      return MaterialApp(home: SignIn());
    });
}

Solution 5:[5]

I don't know if this will be of help for any other person, but my approach was to use Provider and create another class called wrapper, this class will be in charge of switching btw screens without stress... I don't know how you did your auth, But I do all my auth inside another class which I create and name AuthService inside AuthService all auths are done

class Wrapper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final user = Provider.of<UserModels>(context);
    if (user == null) {
      return LoginScreen();
    } else {
      return HomeScreen();
    }
  }
}

Now for your auth

class AuthService {
  final FirebaseAuth _auth = FirebaseAuth.instance;

  //create user object based on firebase user
  UserModels _userFromFirebaseUser(User user) {
    return user != null ? UserModels(uid: user.uid) : null;
  }

  //auth change user stream
  Stream<UserModels> get user {
    return _auth.authStateChanges().map(_userFromFirebaseUser);
  }

// sign in with email and password
  Future signInUser(String email, String pwd, {context}) async {
    try {
      await _auth
          .signInWithEmailAndPassword(email: email, password: pwd)
          .then((result) {
        User user = result.user;
        return _userFromFirebaseUser(user);
      }).catchError((err) {
        if (err.code == 'user-not-found') {
          Flushbar(
            message: "No user found for that email.",
            duration: Duration(seconds: 7),
          )..show(context);
        } else if (err.code == 'wrong-password') {
          Flushbar(
            message: "Wrong password provided for that user.",
            duration: Duration(seconds: 7),
          )..show(context);
        } else {
          Flushbar(
            message: "Internal Error: Something went wrong.",
            duration: Duration(seconds: 7),
          )..show(context);
        }
      });
    } catch (e) {
      print(e.toString());
      return null;
    }
  }
}

Now go to your sign-in button onpress

onPressed: () async {
   var form = formKey.currentState;
    if (form.validate()) {
      setState(() {
        _isChecking = true;
      });
      String email = _email.text;
      String pwd = _pwd.text;

      await _authService
          .signInUser(email, pwd, context: context)
          .then((result) {
        if (result != null) {
          setState(() {
            _isChecking = false;
          });
        } else {
          setState(() {
            _isChecking = false;
          });
        }
      }).catchError((error) {
        setState(() {
          _isChecking = false;
        });
      });
    }
},

This should do all the job for you without you thinking about it too much

Solution 6:[6]

I used Flutter Secure Storage token. I will delete the token when user logoff explicitly so the null check.

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: UserSecureStorage.getAuthToken(),
      builder: (BuildContext context, AsyncSnapshot<String?> token) {
        return GetMaterialApp(
          title: "app",
          home: (token.hasData && token.data != null) ? HomePage() : LoginPage()
        );
      }
    );
  }
}

Solution 7:[7]

it's working for me. null safety supported.

import 'package:firebase_auth/firebase_auth.dart';


FirebaseAuth auth = FirebaseAuth.instance;


FirebaseAuth.instance
    .authStateChanges()
    .listen((User? user) {
  if (user == null) {
    print('User is currently signed out!');
  } else {
    print('User is signed in!');
  }
 });

Solution 8:[8]

use this, I had the same error (Navigator error called on null) and I fixed it.

In your code add await before firebase Auth

just check this solution:

 @override
  void initState() {
    super.initState();
    detectUser();
  }

  void detectUser() async {
    setState(() {
      loading = true;
    });
    FirebaseAuth _auth1 = await FirebaseAuth.instance;

    if (_auth1.currentUser != null) {
      print('email: ' + _auth1.currentUser.email);
      Navigator.push(
        context,
        CupertinoPageRoute(
          builder: (context) => Main(),
        ),
      );
      setState(() {
        loading = false;
      });
    } else {
      //print(user1);
      setState(() {
        loading = false;
      });
    }
  }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3
Solution 4 Vladimir Salguero
Solution 5 Bobby
Solution 6 Prema Pandian
Solution 7 Anwar Kabir
Solution 8 Braken Mohamed