Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firebase Login with Flutter using onAuthStateChanged

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

like image 203
Gilberg Avatar asked Jul 27 '17 14:07

Gilberg


People also ask

Can you connect Flutter with Firebase?

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.


4 Answers

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)

like image 190
Javid Noutash Avatar answered Oct 19 '22 18:10

Javid Noutash


Null safe code (without 3rd party packages)

Screenshot:

enter image description here


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:

like image 33
CopsOnRoad Avatar answered Oct 19 '22 16:10

CopsOnRoad


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

like image 25
humanshado Avatar answered Oct 19 '22 16:10

humanshado


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);
}
like image 25
FrederickCook Avatar answered Oct 19 '22 17:10

FrederickCook