【问题标题】:Flutter Firebase Phone Authentication with BLoC使用 BLoC 进行 Flutter Firebase 电话身份验证
【发布时间】: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 之前完成。因此没有产生身份验证状态,并且用户没有登录。

需要帮助!!!

【问题讨论】:

    标签: firebase flutter async-await firebase-authentication bloc


    【解决方案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 并能够在那里更改状态。
    猜你喜欢
    • 2019-08-05
    • 2020-04-26
    • 2021-06-10
    • 1970-01-01
    • 2018-10-15
    • 2018-07-03
    • 2021-02-12
    • 2020-01-23
    • 1970-01-01
    相关资源
    最近更新 更多