使用 BLoC 进行 Flutter Firebase 电话身份验证

Posted

技术标签:

【中文标题】使用 BLoC 进行 Flutter Firebase 电话身份验证【英文标题】:Flutter Firebase Phone Authentication with BLoC 【发布时间】:2020-02-21 22:21:53 【问题描述】:

我正在尝试使用 BLoC 模式实现 Firebase 电话身份验证。

这是我的集体课

class AuthBloc extends Bloc<AuthEvent, AuthState> 
  final AuthProvider authProvider;

  AuthBloc(this.authProvider) : assert(authProvider!= null);

  @override
  AuthState get initialState => Uninitialized();

  @override
  Stream<AuthState> mapEventToState(AuthEvent event) async* 
    if (event is AppLaunched) 
      yield* _mapAppLaunchedToState();
     else if(event is OtpRequested) 
      yield* _mapOtpRequestedToState();
     else if (event is LoggedIn) 
      yield* _mapLoggedInToState();
     else if (event is LoggedOut) 
      yield* _mapLoggedOutToState();
    
  

  Stream<AuthState> _mapAppLaunchedToState() async* 
    try 
      final isSignedIn = await authProvider.isLoggedIn();

      if (isSignedIn) 
        final name = userProvider.firebaseUser;
        yield Authenticated(name);
       else 
        yield Unauthenticated();
      
     catch (_) 
      yield Unauthenticated();
    
  

  Stream<AuthState> _mapOtpRequestedTostate() async* 
    yield AuthInProgress();
    try 
      FirebaseUser firebaseUser = await authProvider.verifyPhone();

      if (firebaseUser != null) 
        yield Authenticated(firebaseUser);
       else 
        yield Unauthenticated();
      
     catch(_, stacktrace) 
      yield Unauthenticated();
    
  

  Stream<AuthState> _mapLoggedInToState() async* 
    yield Authenticated(userProvider.firebaseUser);
  

  Stream<AuthState> _mapLoggedOutToState() async* 
    yield Unauthenticated();
    authProvider.signOutUser();
  

这是 AuthProvider

class AuthProvider extends BaseAuthProvider 
  String _verificationId;
  FirebaseUser user;
  final FirebaseAuth _firebaseAuth;

  AuthProvider(
      FirebaseAuth firebaseAuth)
      : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance;

  @override
  Future<FirebaseUser> verifyPhone() async 
    final PhoneVerificationCompleted verificationCompleted =
        (AuthCredential phoneAuthCredential) async 
          user = (await _firebaseAuth.signInWithCredential(phoneAuthCredential)).user;
    ;

    final PhoneVerificationFailed verificationFailed =
        (AuthException authException) 
      print(
          'Phone number verification failed. Code: $authException.code. Message: $authException.message');
    ;

    final PhoneCodeSent codeSent =
        (String verificationId, [int forceResendingToken]) async 
      _verificationId = verificationId;
    ;

    final PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout =
        (String verificationId) 
      _verificationId = verificationId;
    ;

    await _firebaseAuth.verifyPhoneNumber(
        phoneNumber: _phoneNumberProvider.number,
        timeout: const Duration(seconds: 5),
        verificationCompleted: verificationCompleted,
        verificationFailed: verificationFailed,
        codeSent: codeSent,
        codeAutoRetrievalTimeout: codeAutoRetrievalTimeout);

    return user;
  

  Future<FirebaseUser> signInWithPhone() async 
    final AuthCredential credential = PhoneAuthProvider.getCredential(
      verificationId: _verificationId,
      smsCode: _otpProvider.number,
    );
    final FirebaseUser user =
        (await _firebaseAuth.signInWithCredential(credential)).user;
    final FirebaseUser currentUser = await _firebaseAuth.currentUser();
    assert(user.uid == currentUser.uid);

    if (user != null) 
      return currentUser;
     else 
      return null;
    
  

  @override
  Future<void> signOutUser() async 
    return Future.wait([_firebaseAuth.signOut()]); // terminate the session
  

  @override
  Future<FirebaseUser> getCurrentUser() async 
    return await _firebaseAuth.currentUser(); //retrieve the current user
  

  @override
  Future<bool> isLoggedIn() async 
    final user =
        await _firebaseAuth.currentUser(); //check if user is logged in or not
    return user != null;
  

  @override
  void dispose() 

当来自 AuthBloc 的 verifyPhone 被调用时,它会异步执行,然后又会调用再次异步的 mcallbacks。所以 _mapOtpRequestedToState() 将在我们从 AuthProvider 取回 FirebaseUser 之前完成。因此没有产生身份验证状态,并且用户没有登录。

需要帮助!!!

【问题讨论】:

【参考方案1】:

我认为大多数时候,可读的代码要好得多。

以下示例使用(Action -> Event)机制实现了您打算编写的逻辑:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Provider<AppStateBloc>(
      builder: (_) => AppStateBloc(),
      dispose: (_, bloc) 
        bloc.dispose();
      ,
      child: MaterialApp(
        home: TestPage(),
      ),
    );
  


class TestPage extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    AppStateBloc appStateBloc = Provider.of<AppStateBloc>(context, listen: false);

    return Scaffold(
      appBar: AppBar(title: Text('Flow Test')),
      body: Column(
        children: <Widget>[
          StreamBuilder<AppState>(
            stream: appStateBloc.stateOut,
            initialData: AppState.initial,
            builder: (BuildContext context, AsyncSnapshot<AppState> snapshot) 
              AppState state = snapshot.data;

              return Column(
                children: <Widget>[
                  Text('Current State: $state'),
                  SizedBox(height: 10.0),
                  if (state == AppState.initial || state == AppState.failure)
                    RaisedButton(
                      onPressed: () => appStateBloc.actionIn(AppStateAction.login),
                      child: Text('Authenticate'),
                    ),

                  if (state == AppState.authenticated)
                    RaisedButton(
                      onPressed: () => appStateBloc.actionIn(AppStateAction.logout),
                      child: Text('Logout'),
                    ),

                ],
              );
            ,
          ),
        ],
      ),
    );
  


class AppStateBloc 
  StreamController<AppState> _controllerState = StreamController<AppState>.broadcast();
  Stream<AppState> get stateOut => _controllerState.stream;
  Function(AppState) get _stateIn => _controllerState.sink.add;

  StreamController<AppStateAction> _controllerAction = StreamController<AppStateAction>.broadcast();
  Function(AppStateAction) get actionIn => _controllerAction.sink.add;

  StreamSubscription _subscription;

  AppStateBloc() 
    _subscription = _controllerAction.stream.listen(_businessLogic);
  

  // All the business logic comes here
  void _businessLogic(AppStateAction action) async 
    switch (action) 

      case AppStateAction.login:
        // do authentication
        User user = await fakeAuthenticator.verifyUser();
        if (user == null) 
          _stateIn(AppState.failure);
         else 
          _stateIn(AppState.authenticated);
        
        break;

      case AppStateAction.logout:
        // do what needs to be done in this case
        await fakeAuthenticator.logout();
        _stateIn(AppState.initial);
        break;

      default:
        // nothing
        break;
    
  

  void dispose() 
    _subscription?.cancel();
    _controllerAction?.close();
    _controllerState?.close();
  


enum AppStateAction 
  none,
  login,
  logout,


enum AppState 
  initial,
  authenticated,
  failure,


class User 

class FakeAuthenticator 
  User _user;

  Future<User> verifyUser() async 
    // Simulation of Authentication made at server side
    await Future.delayed(const Duration(seconds: 1));

    // Successful authentication
    _user = User();

    return _user;
  

  Future<void> logout() async 
    // Simulation of Authentication made at server side
    await Future.delayed(const Duration(seconds: 1));

    _user = null;
  

  User get user => _user;

  // ------- Singleton
  static final FakeAuthenticator _instance = FakeAuthenticator._internal();
  factory FakeAuthenticator() => _instance;
  FakeAuthenticator._internal();


FakeAuthenticator fakeAuthenticator = FakeAuthenticator();

与您的代码的主要区别在于,使用此代码,但这是我个人的感觉,您可以更好地“控制”您的业务逻辑。

【讨论】:

感谢您的建议。我能够登录用户。我使用 Singleton 类使其工作。 你能帮我看看你是怎么做到的吗?我也面临同样的问题。 抱歉回复晚了。我有我的学期考试。遇到问题,我做了我的 AuthBloc 单例,这样我就可以传递同一个对象。通过这个,我能够将 authbloc 对象发送到 authprovider 并能够在那里更改状态。

以上是关于使用 BLoC 进行 Flutter Firebase 电话身份验证的主要内容,如果未能解决你的问题,请参考以下文章

使用 Bloc/Cubit 进行 Flutter 状态管理

Flutter BLoC:管理主要数据类型的状态

Flutter BLoC 测试

如何使用flutter_bloc处理动画

一起使用flutter bloc库和websockets的设计建议

Flutter Bloc 框架 实现 HTTP + JSON 通讯