使用 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 电话身份验证的主要内容,如果未能解决你的问题,请参考以下文章