Firebase Login with Flutter using onAuthStateChanged
I know this question is pretty old, but here is the answer if anybody is still looking for it.
Firebase returns a Stream
of FirebaseUser
with it's onAuthStateChanged
function. There are many ways to listen to the user's authentication state change. This is how I do it:
Solution 1
I return a StreamBuilder
to my App's home page, and the StreamBuilder
returns specific pages based on the auth status of the user.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Your App Name',
home: _getLandingPage()
);
}
Widget _getLandingPage() {
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.providerData.length == 1) { // logged in using email and password
return snapshot.data.isEmailVerified
? MainPage()
: VerifyEmailPage(user: snapshot.data);
} else { // logged in using other providers
return MainPage();
}
} else {
return LoginPage();
}
},
);
}
Solution 2
You can create a listener in your app's initState()
function as well. Make sure the firebase app has been initialized before registering the listener.
@override
void initState() {
super.initState();
FirebaseAuth.instance.authStateChanges().listen((firebaseUser) {
// do whatever you want based on the firebaseUser state
});
}
Solution 3 (Update May 2021)
A simple approach with null-safety without using the provider package:
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(App());
}
class App extends StatefulWidget {
@override
_AppState createState() => _AppState();
}
/// State is persistent and not rebuilt, therefore [Future] is only created once.
/// If [StatelessWidget] is used, in the event where [App] is rebuilt, that
/// would re-initialize FlutterFire and makes our app re-enter the
/// loading state, which is undesired.
class _AppState extends State<App> {
final Future<FirebaseApp> _initFirebaseSdk = Firebase.initializeApp();
final _navigatorKey = new GlobalKey<NavigatorState>();
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
navigatorKey: _navigatorKey,
theme: theme(),
home: FutureBuilder(
future: _initFirebaseSdk,
builder: (_, snapshot) {
if (snapshot.hasError) return ErrorScreen();
if (snapshot.connectionState == ConnectionState.done) {
// Assign listener after the SDK is initialized successfully
FirebaseAuth.instance.authStateChanges().listen((User? user) {
if (user == null)
_navigatorKey.currentState!
.pushReplacementNamed(LoginScreen.routeName);
else
_navigatorKey.currentState!
.pushReplacementNamed(HomeScreen.routeName);
});
}
return LoadingScreen();
}),
routes: routes,
);
}
}
This approach guarantees that you only use Firebase authentication FirebaseAuth.instance.authStateChanges().listen()
after the SDK completes initialization. The auth change listener will be first invoked on app launch and then automatically called again after logout and login.
.pushReplacementNamed()
will move to a new screen without back (no back icon on the app bar)
Null safe code (without 3rd party packages)
Screenshot:
To check if the user is signed in from anywhere in the app, use
bool signedIn = Auth.instance.isSignedIn;
To sign in, use
await Auth.instance.signIn(email: 'email', password: 'password');
To sign out, use
await Auth.instance.signOut();
Full Code:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MaterialApp(
home: StreamBuilder<User?>(
stream: Auth.instance.authStateChange(),
builder: (_, snapshot) {
final isSignedIn = snapshot.data != null;
return isSignedIn ? HomePage() : LoginPage();
},
),
),
);
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('HomePage')),
body: Center(
child: ElevatedButton(
onPressed: () => Auth.instance.signOut(),
child: Text('Sign out'),
),
),
);
}
}
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('LoginPage')),
body: Center(
child: ElevatedButton(
onPressed: () => Auth.instance.signIn(email: '[email protected]', password: 'test1234'),
child: Text('Sign in'),
),
),
);
}
}
class Auth {
static final instance = Auth._();
Auth._();
final FirebaseAuth _auth = FirebaseAuth.instance;
bool get isSignedIn => _auth.currentUser != null;
Stream<User?> authStateChange() => _auth.authStateChanges();
Future<void> signIn({required String email, required String password}) => _auth.signInWithEmailAndPassword(email: email, password: password);
Future<void> signOut() => _auth.signOut();
}
Same code using provider
package:
Check this answer: