'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 |