Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to manage Firebase Authentication state in Flutter?

I have a WelcomeScreen which contains sign up and login and the HomeScreen where I want to redirect after the user logs in. To manage auth data, I have created an auth.dart with static properties and methods so I can access them across all pages with same data.

import 'package:firebase_auth/firebase_auth.dart';

class Auth {

  static final auth = FirebaseAuth.instance;

  static Future<void> logout() async {
    await auth.signOut();
  }

  static Future<void> loginUser(String userEmail, String userPassword) async {
    await auth.signInWithEmailAndPassword(email: userEmail, password: userPassword);
  }

  static Future<FirebaseUser> getCurrentUser() async {
    return await auth.currentUser();
  }
}

In main.dart file, I am using StreamBuilder to change the current screen based on changing auth data. I got this StreamBuilder code from this answer.

home: StreamBuilder<FirebaseUser>(
  stream: Auth.auth.onAuthStateChanged,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return HomeScreen();
    } else {
      return WelcomeScreen();
    }
  },
),

In my login screen, I am using the below code to trigger log in:

Future<void> login() async {
    ...

    try {
      await Auth.loginUser(userEmail, userPassword);
      var user =  await Auth.getCurrentUser();
      print(user.displayName); // This works
    } catch (error) {
      print(error.message);
    }
  }

I don't know whether the static methods I am using are the correct way to handle Firebase auth or not but it seems to work. After logging in, I am able to display the name of the logged in user but the StreamBuilder in main.dart is not reflecting the updated auth data, i.e not changing the page.

Is it because of static methods or something wrong in the implementation of StreamBuilder?

like image 840
gegobyte Avatar asked May 21 '20 10:05

gegobyte


People also ask

What is Firebase Auth in flutter?

Firebase Auth provides many methods and utilities for enabling you to integrate secure authentication into your new or existing Flutter application. In many cases, you will need to know about the authentication state of your user, such as whether they're logged in or logged out.

How do I subscribe to Firebase authentication state?

Firebase Auth enables you to subscribe in realtime to this state via a Stream. Once called, the stream provides an immediate event of the user's current authentication state, and then provides subsequent events whenever the authentication state changes. There are three methods for listening to authentication state changes:

How do I set the persistence settings in Firebase?

To configure these settings, call the setPersistence () method (note; on native platforms an UnimplementedError will be thrown): Firebase provides a number of ways to sign users into your application, from anonymous users, password authentication, phone authentication and using OAuth/social providers.

How do I create an anonymous user in firebaseauth?

To get started, call the signInAnonymously () method on the FirebaseAuth instance: UserCredential userCredential = await FirebaseAuth.instance.signInAnonymously (); Once successfully resolved, the user will be granted an anonymous account. If you are listening to changes in authentication state, a new event will be sent to your listeners.


3 Answers

Screenshot:

enter image description here


I'm not sure how you were doing it, so I added a minimal working code, I didn't make any changes to your Auth class. Although it is a good idea to use Provider but you can get things done with static method also.

Edited code:

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MaterialApp(home: MyApp()));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<FirebaseUser>(
      stream: Auth.auth.onAuthStateChanged,
      builder: (context, snapshot) {
        if (snapshot.hasData) return HomeScreen();
         else return WelcomeScreen();
      },
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home Screen')),
      floatingActionButton: FloatingActionButton.extended(
        label: Text('Sign out'),
        onPressed: Auth.logout,
      ),
    );
  }
}

class WelcomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Welcome Screen')),
      body: Center(
        child: RaisedButton(
          onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => LoginPage())),
          child: Text('Go to Login Page'),
        ),
      ),
    );
  }
}

class LoginPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Login Page')),
      body: Center(
        child: RaisedButton(
          onPressed: () async {
            await Auth.loginUser('[email protected]', 'test1234');
            await Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (_) => MyApp()), (_) => false);
          },
          child: Text('Login'),
        ),
      ),
    );
  }
}
like image 133
CopsOnRoad Avatar answered Oct 19 '22 01:10

CopsOnRoad


In my opinion the best way to manage firebase authentication in flutter is to use the provider package. Your Auth class is missing one important thing which is the onAuthStateChnaged method. You can create a stream as a getter for the onAuthStateChanged inside an Auth class. The Auth class will extend the ChangeNotifier class. ChangeNotifier class is part of the flutter api.

class Auth extends ChangeNotifier {

    final FirebaseAuth _auth = FirebaseAuth.instance;

    // create a getter stream
    Stream<FirebaseUser> get onAuthStateChanged => _auth.onAuthStateChanged;

    //Sign in async functions here ..

}

Wrap your MaterialApp with ChangeNotifierProvider (part of provider package) and return an instance of the Auth class in create method like so:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => Auth(),
      child: new MaterialApp(
      home: Landing(),
      ),
    );
  }
}

Now create landing page as a stateless widget. Use a Consumer or 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) {
    Auth auth = Provider.of<Auth>(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 40
humanshado Avatar answered Oct 19 '22 00:10

humanshado


I made a video(https://youtu.be/iqy7xareuAI) discussing this bounty and taking you through the steps of implementing the app that you want. All it needs is a simple StreamBuilder and a FutureBuilder.

More complex tools like provider and singleton pattern(what you are trying to achieve with static classes) can be applied for more complex applications, but not needed here.

  1. We treat WelcomeScreen as the screen that decides between LoginSignupScreen and HomeScreen
  2. We use StreamBuilder for WelcomeScreen
  3. We use FutureBuilder for HomeScreen.

Here is the code for the WelcomeScreen:

import 'package:ctfultterfireexperiments/src/screens/home_screen.dart';
import 'package:ctfultterfireexperiments/src/screens/login_signup_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';

class WelcomeScreen extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<FirebaseUser>(
      stream: FirebaseAuth.instance.onAuthStateChanged,
      builder: (BuildContext _, AsyncSnapshot<FirebaseUser> snapshot) {
        //if the snapshot is null, or not has data it is signed out
        if(! snapshot.hasData) return LoginSignupScreen();
        // if the snapshot is having data it is signed in, show the homescreen
        return HomeScreen();
      },
    );
  }
}

Here is the code for HomeScreen.dart

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Container(
          child: Center(
              child: FutureBuilder(
            builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
              if(!snapshot.hasData) return LinearProgressIndicator();
              return Text("Home Screen: ${snapshot.data.displayName}");
            },
            future: FirebaseAuth.instance.currentUser(),
          )),
        ),
        Spacer(),
        RaisedButton(onPressed: () {FirebaseAuth.instance.signOut();})
      ],
    );
  }
}

Here is the code for LoginSignupScreen.dart:

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';

class LoginSignupScreen extends StatelessWidget {

  login() async{
    final GoogleSignIn _googleSignIn = GoogleSignIn();
    final _auth = FirebaseAuth.instance;
    final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
    final GoogleSignInAuthentication googleAuth = await googleUser.authentication;

    final AuthCredential credential = GoogleAuthProvider.getCredential(
      accessToken: googleAuth.accessToken,
      idToken: googleAuth.idToken,
    );

    final FirebaseUser user = (await _auth.signInWithCredential(credential)).user;
    print("signed in " + user.displayName);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Spacer(flex: 1,),
        Text("Login/Signup Screen"),
        Spacer(flex: 2,),
        RaisedButton(onPressed: login)
      ],
    );
  }
}

This will work as a minimum working example.

like image 1
Raveesh Agarwal Avatar answered Oct 18 '22 23:10

Raveesh Agarwal