简体   繁体   中英

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, ie not changing the page.

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

Null safe code (using provider )

在此处输入图像描述


Full code:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  runApp(
    ChangeNotifierProvider<AuthModel>(
      create: (_) => AuthModel(),
      child: MaterialApp(
        home: Consumer<AuthModel>(
          builder: (_, auth, __) => auth.isSignedIn ? HomePage() : WelcomePage(),
        ),
      ),
    ),
  );
}

class WelcomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('WelcomePage')),
      body: Center(
        child: ElevatedButton(
          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: ElevatedButton(
          onPressed: () async {
            final model = context.read<AuthModel>();
            await model.signIn(email: 'test@test.com', password: 'test1234');
            Navigator.pop(context);
          },
          child: Text('Login'),
        ),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('HomePage')),
      body: Center(
        child: FloatingActionButton.extended(
          onPressed: () async {
            final model = context.read<AuthModel>();
            await model.signOut();
          },
          label: Text('Log out'),
        ),
      ),
    );
  }
}

class AuthModel extends ChangeNotifier {
  final FirebaseAuth _auth = FirebaseAuth.instance;

  bool get isSignedIn => _auth.currentUser != null;

  Future<void> signIn({required String email, required String password}) async {
    await _auth.signInWithEmailAndPassword(email: email, password: password);
    notifyListeners();
  }

  Future<void> signOut() async {
    await _auth.signOut();
    notifyListeners();
  }
}

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

Screenshot:

在此处输入图像描述


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('test@test.com', 'test1234');
            await Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (_) => MyApp()), (_) => false);
          },
          child: Text('Login'),
        ),
      ),
    );
  }
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM