Outside of Flutter, when I implement firebase authentication I always use the onAuthStateChanged listener provided by firebase to determine if the user is logged in or not and respond accordingly.
I am trying to do something similar using flutter, but I can find a way to access onAuthStateChanged of Firebase. I am using the firebase_auth, and google_signin Flutter plugins. I am working of example code that is included with the firebase_auth Flutter plugin. Below is the sample code. I can login successfully with google sign in, but the example is too simple, because I want to have an observer/listener to detect the user's signed in/out state.
Is there a way to detect via observer/listener using the firebase_auth/google_signin flutter plugins to determine the status of a user?
Ultimately I want the app to determine if the user is logged in (yes/no). If not then show a login screen, if yes then show my main app page.
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = new GoogleSignIn();
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Firebase Auth Demo',
home: new MyHomePage(title: 'Firebase Auth Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<String> _message = new Future<String>.value('');
Future<String> _testSignInAnonymously() async {
final FirebaseUser user = await _auth.signInAnonymously();
assert(user != null);
assert(user == _auth.currentUser);
assert(user.isAnonymous);
assert(!user.isEmailVerified);
assert(await user.getToken() != null);
if (Platform.isIOS) {
// Anonymous auth doesn't show up as a provider on iOS
assert(user.providerData.isEmpty);
} else if (Platform.isAndroid) {
// Anonymous auth does show up as a provider on Android
assert(user.providerData.length == 1);
assert(user.providerData[0].providerId == 'firebase');
assert(user.providerData[0].uid != null);
assert(user.providerData[0].displayName == null);
assert(user.providerData[0].photoUrl == null);
assert(user.providerData[0].email == null);
}
return 'signInAnonymously succeeded: $user';
}
Future<String> _testSignInWithGoogle() async {
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final FirebaseUser user = await _auth.signInWithGoogle(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
assert(user.email != null);
assert(user.displayName != null);
assert(!user.isAnonymous);
assert(await user.getToken() != null);
return 'signInWithGoogle succeeded: $user';
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new MaterialButton(
child: const Text('Test signInAnonymously'),
onPressed: () {
setState(() {
_message = _testSignInAnonymously();
});
}),
new MaterialButton(
child: const Text('Test signInWithGoogle'),
onPressed: () {
setState(() {
_message = _testSignInWithGoogle();
});
}),
new FutureBuilder<String>(
future: _message,
builder: (_, AsyncSnapshot<String> snapshot) {
return new Text(snapshot.data ?? '',
style: const TextStyle(
color: const Color.fromARGB(255, 0, 155, 0)));
}),
],
),
);
}
}
Here are links to the flutter packages in question: https://github.com/flutter/plugins/tree/master/packages/firebase_auth https://github.com/flutter/plugins/tree/master/packages/google_sign_in
Since Flutter is a multi-platform framework, each Firebase plugin is applicable for Apple, Android, and web platforms. So, if you add any Firebase plugin to your Flutter app, it will be used by the Apple, Android, and web versions of your app.
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:
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();
}
},
);
}
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
});
}
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)
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:
You can create a stream as a getter for the onAuthStateChanged inside an AuthService class. To help you manage the state, you can use the Provider package. The AuthService class will extend the ChangeNotifier class.
class AuthService extends ChangeNotifier {
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = new GoogleSignIn();
// create a getter stream
Stream<FirebaseUser> get onAuthStateChanged => _auth.onAuthStateChanged;
//Sign in async functions here ..
}
Wrap your MaterialApp with ChangeNotifierProvider and return an instance of the AuthService class in create method like so:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => AuthService(),
child: new MaterialApp(
title: 'Firebase Auth Demo',
home: Landing(),
),
);
}
}
Now create landing page as a stateless widget. Use Provider.of(context) and a stream builder to listen to the auth changes and render the login page or home page as appropriate.
class Landing extends StatelessWidget {
@override
Widget build(BuildContext context) {
AuthService auth = Provider.of<AuthService>(context);
return StreamBuilder<FirebaseUser>(
stream: auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
FirebaseUser user = snapshot.data;
if (user == null) {
return LogIn();
}
return Home();
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
}
You can read more about state management with provider from the official flutter documentation. Follow this link: https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
The Firebase for Flutter Codelab has a much more in-depth example using Google sign in and Firebase auth.
After the final step you end up with this _ensureLoggedIn
function that is used to check whether the user is signed in and if not, initiate a sign in flow.
Future<Null> _ensureLoggedIn() async {
GoogleSignInAccount user = googleSignIn.currentUser;
if (user == null)
user = await googleSignIn.signInSilently();
if (user == null) {
user = await googleSignIn.signIn();
analytics.logLogin();
}
if (auth.currentUser == null) {
GoogleSignInAuthentication credentials =
await googleSignIn.currentUser.authentication;
await auth.signInWithGoogle(
idToken: credentials.idToken,
accessToken: credentials.accessToken,
);
}
}
You could modify this to check these things when your app starts up and conditionally show different views to pre-auth and post-auth users with something like:
final auth = FirebaseAuth.instance;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'MyApp',
home: (_checkLogin() == true ? new PostAuthScaffold() : new PreAuthScaffold())
);
}
}
bool _checkLogin() {
GoogleSignInAccount user = googleSignIn.currentUser;
return !(user == null && auth.currentUser == null);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With