如何在Flutter中管理Firebase身份验证状态?

时间:2020-05-21 10:03:50

标签: flutter dart firebase-authentication

我有一个WelcomeScreen,其中包含注册和登录,以及HomeScreen,我想在用户登录后重定向到该位置。要管理身份验证数据,我创建了一个auth.dart static属性和方法,这样我就可以在所有具有相同数据的页面上访问它们。

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();
  }
}

main.dart文件中,我正在使用StreamBuilder基于更改身份验证数据来更改当前屏幕。我从this answer获得了这个StreamBuilder代码。

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

在我的登录屏幕中,我正在使用以下代码触发登录:

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);
    }
  }

我不知道我使用的静态方法是否是处理Firebase身份验证的正确方法,但它似乎有效。登录后,我可以显示已登录用户的名称,但是StreamBuilder中的main.dart不能反映更新的身份验证数据,即不更改页面。

是因为静态方法还是在StreamBuilder的实现中出现问题?

4 个答案:

答案 0 :(得分:9)

enter image description here

[我将其添加为单独的答案,因为它使用了提供程序包]

您应该使用static或其他一些模型来代替在Auth类中创建变量和方法Provider。我在bool类中使用Auth变量来跟踪登录,您可以根据需要修改该类。

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(
    ChangeNotifierProvider<Auth>(
      create: (_) => Auth(),
      child: MaterialApp(home: MyApp()),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<Auth>(
      builder: (_, auth, __) {
        if (auth.loggedIn) return HomeScreen();
        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: () async {
          final auth = Provider.of<Auth>(context, listen: false);
          await 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.pushReplacement(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 {
            final auth = Provider.of<Auth>(context, listen: false);
            await auth.loginUser('test@test.com', 'test1234');
            await Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => MyApp()));
          },
          child: Text('Login'),
        ),
      ),
    );
  }
}

class Auth with ChangeNotifier {
  final _auth = FirebaseAuth.instance;
  bool _loggedIn = false;

  bool get loggedIn => _loggedIn;

  Future<void> logout() async {
    await _auth.signOut();
    _loggedIn = false;
    notifyListeners();
  }

  Future<void> loginUser(String userEmail, String userPassword) async {
    await _auth.signInWithEmailAndPassword(email: userEmail, password: userPassword);
    _loggedIn = true;
    notifyListeners();
  }

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

现在您可以看到,即使您不是从LoginPage的直接子代Consumer登录,也可以看到它的builder仍在被调用登录状态更改。但是,当您导航到LoginPage时,由于窗口小部件树仅显示LoginPage,因此需要弹出弹出窗口以返回上一页。在上一个示例中,我导航到HomeScreen,但在此示例中,我弹出。

答案 1 :(得分:3)

屏幕截图:

enter image description here


我不确定您的工作方式,因此我添加了一个最小的工作代码,没有对您的Auth类进行任何更改。尽管使用Provider是个好主意,但是您也可以使用static方法来完成工作。

修改后的代码:

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'),
        ),
      ),
    );
  }
}

答案 2 :(得分:1)

我制作了一个视频(https://youtu.be/iqy7xareuAI),讨论了这笔赏金,并带您完成了实现所需应用程序的步骤。它所需要的只是一个简单的StreamBuilder和一个FutureBuilder

providersingleton pattern这样的更复杂的工具(您试图通过静态类来实现)可以应用于更复杂的应用程序,但此处不需要。

  1. 我们将WelcomeScreen视为在LoginSignupScreen和HomeScreen之间进行选择的屏幕
  2. 我们将StreamBuilder用于WelcomeScreen
  3. 我们将FutureBuilder用于HomeScreen。

这是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();
      },
    );
  }
}

这是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();})
      ],
    );
  }
}

这是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)
      ],
    );
  }
}

这将作为最小的工作示例。

答案 3 :(得分:0)

我认为在Flutter中管理Firebase身份验证的最佳方法是使用提供程序包。您的Auth类缺少一件重要的事情,即onAuthStateChnaged方法。您可以在Auth类中创建流作为onAuthStateChanged的获取器。 Auth类将扩展ChangeNotifier类。 ChangeNotifier类是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 ..

}

用ChangeNotifierProvider(提供程序包的一部分)包装MaterialApp,并在create方法中返回Auth类的实例,如下所示:

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

现在将登录页面创建为无状态窗口小部件。使用Consumer或Provider.of(上下文)和流构建器来侦听auth更改,并根据需要呈现登录页面或主页。

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(),
            ),
          );
        }
      },
    );
  }
}

您可以从Flutter官方文档中了解有关提供者的状态管理的更多信息。请点击以下链接:https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple