我正在实施FirebaseAuth验证电话以适应本指南https://medium.com/@tapanrgohil/firebase-phone-authentication-in-flutter-with-bloc-pattern-4ddc2d43d76c,因为我不想登录,而只是将新的PhoneAuthentication链接到现有用户,而我使用的是AuthenticationBloc
而不是Bloc在指南中。
我从PaymentScreen
开始电话验证过程,并尝试直接在AuthenticationBloc
的{{1}}中提供PaymentScreen
,以为创建一个新的MultiBlocProvider
,但是错误是相同的。
在AuthenticationBloc
中,内部AuthenticationBloc
基本上负责所有电话验证事件。 StreamController
的{{1}}中的传入States
只会弹出并显示对话框,就像PaymentScreen
出现手动otp插入对话框,错误,错误的otp等情况一样。
为了找出导致不良状态的原因,我首先注释掉了所有上下文弹出消息,以确保不是那样,然后注释掉了流中的所有BlocListener
。
这些是从控制台打印出来的:
AutoRetrieveCodeTimeout
您能发现关闭集团的原因吗?
非常感谢。
.close()
I/flutter ( 7710): VerifyPhoneNumberEvent received
I/flutter ( 7710): _mapVerifyPhoneNumberToState started
I/BiChannelGoogleApi( 7710): [FirebaseAuth: ] getGoogleApiForMethod() returned Gms: com.google.firebase.auth.api.internal.zzaq@7f6fccb
I/flutter ( 7710): _mapVerifyPhoneNumberToState PhoneCodeSent
I/flutter ( 7710): PhoneCodeSentEvent received
I/flutter ( 7710): _mapVerifyPhoneNumberToState PhoneCodeAutoRetrievalTimeout
I/flutter ( 7710): Bloc error is Bad state: Cannot add new events after calling close
I/flutter ( 7710): Bloc error is Bad state: Cannot add new events after calling close
I/flutter ( 7710): Bloc error is Bad state: Cannot add new events after calling close
I/flutter ( 7710): Bloc error is Bad state: Cannot add new events after calling close
I/flutter ( 7710): Bloc error is Bad state: Cannot add new events after calling close
I/flutter ( 7710): Bloc error is Bad state: Cannot add new events after calling close
class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
final UserRepository _userRepository;
AuthenticationBloc({@required UserRepository userRepository})
: assert(userRepository != null),
_userRepository = userRepository;
StreamSubscription subscription;
String verificationId = "";
@override
AuthenticationState get initialState => Uninitialized();
@override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event) async* {
if (event is StartApp) {
yield* _startAppToState();
}
if (event is AppStarted) {
yield* _mapAppStartedToState();
} else if (event is LoggedIn) {
yield* _mapLoggedInToState();
} else if (event is LoggedOut) {
yield* _mapLoggedOutToState();
}
// phone verification
if (event is VerifyPhoneNumberEvent) {
print('VerifyPhoneNumberEvent received');
yield VerifyingState();
subscription = _mapVerifyPhoneNumberToState(event.phoneNumber).listen((event) {
add(event);
});
} else if (event is PhoneCodeSentEvent) {
print('PhoneCodeSentEvent received');
yield OtpSentState();
} else if (event is VerificationCompletedEvent) {
print('VerificationCompletedEvent received');
yield VerificationCompleteState(firebaseUser: event.firebaseUser, isVerified: event.isVerified);
} else if (event is VerificationExceptionEvent) {
print('VerificationExceptionEvent received');
yield VerificationExceptionState(message: event.message);
} else if (event is VerifySmsCodeEvent) {
print('VerifySmsCodeEvent received');
yield VerifyingState();
try {
AuthResult result =
await _userRepository.verifyAndLinkAuthCredentials(verificationId: verificationId, smsCode: event.smsCode);
if (result.user != null) {
yield VerificationCompleteState(firebaseUser: result.user, isVerified: true);
} else {
yield OtpExceptionState(message: "Invalid otp!",verificationId: verificationId);
}
} catch (e) {
yield OtpExceptionState(message: "Invalid otp!", verificationId: verificationId);
print(e);
}
} else if ( event is PhoneCodeAutoRetrievalTimeoutEvent){
yield PhoneCodeAutoRetrievalTimeoutState(verificationId: event.verificationId);
}
if(event is SendVerificationCodeEvent) {
yield*_mapVerificationCodeToState(event);
}
}
Stream<AuthenticationEvent> _mapVerifyPhoneNumberToState(String phoneNumber) async* {
print('_mapVerifyPhoneNumberToState started');
StreamController<AuthenticationEvent> phoneVerificationStreamController = StreamController();
final phoneVerificationCompleted = (AuthCredential authCredential) {
print('_mapVerifyPhoneNumberToState PhoneVerificationCompleted');
// _userRepository.getUser();
_userRepository.getCurrentUser().catchError((onError) {
print(onError);
}).then((user) {
phoneVerificationStreamController.add(VerificationCompletedEvent(firebaseUser: user, isVerified: true));
// phoneVerificationStreamController.close();
});
};
final phoneVerificationFailed = (AuthException authException) {
print('_mapVerifyPhoneNumberToState PhoneVerificationFailed');
print(authException.message);
phoneVerificationStreamController.add(VerificationExceptionEvent(onError.toString()));
// phoneVerificationStreamController.close();
};
final phoneCodeSent = (String verificationId, [int forceResent]) {
print('_mapVerifyPhoneNumberToState PhoneCodeSent');
this.verificationId = verificationId;
phoneVerificationStreamController.add(PhoneCodeSentEvent());
};
final phoneCodeAutoRetrievalTimeout = (String verificationId) {
// after this print Bloc error is Bad state: Cannot add new events after calling close
print('_mapVerifyPhoneNumberToState PhoneCodeAutoRetrievalTimeout');
this.verificationId = verificationId;
// phoneVerificationStreamController.close();
// phoneVerificationStreamController.add(PhoneCodeAutoRetrievalTimeoutEvent(verificationId: verificationId));
};
await _userRepository.verifyPhone(
phoneNumber: phoneNumber,
timeOut: Duration(seconds: 0), // 0 triggers PhoneCodeAutoRetrievalTimeout immediately
phoneVerificationFailed: phoneVerificationFailed,
phoneVerificationCompleted: phoneVerificationCompleted,
phoneCodeSent: phoneCodeSent,
autoRetrievalTimeout: phoneCodeAutoRetrievalTimeout);
yield* phoneVerificationStreamController.stream;
}
Stream<AuthenticationState> _startAppToState() async* {
Timer(Duration(seconds: 5), () {
add(AppStarted());
});
}
Stream<AuthenticationState> _mapAppStartedToState() async* {
try {
final isSignedIn = await _userRepository.isSignedIn();
if (isSignedIn) {
final user = await _userRepository.getUser();
yield Authenticated(user);
} else {
yield Unauthenticated();
}
} catch (_) {
yield Unauthenticated();
}
}
Stream<AuthenticationState> _mapLoggedInToState() async* {
yield Authenticated(await _userRepository.getUser());
}
Stream<AuthenticationState> _mapLoggedOutToState() async* {
yield Unauthenticated();
_userRepository.signOut();
}
Stream<AuthenticationState> _mapVerificationCodeToState(SendVerificationCodeEvent event) async* {
print('_mapVerificationCodeToState started');
yield VerifyingState();
try {
AuthResult result =
await _userRepository.verifyAndLinkAuthCredentials(verificationId: verificationId, smsCode: event.smsCode);
if (result.user != null) {
yield VerificationCompleteState(firebaseUser: result.user, isVerified: true);
} else {
yield OtpExceptionState(message: "Invalid otp!", verificationId: verificationId);
}
} catch (e) {
yield OtpExceptionState(message: "Invalid otp!", verificationId: verificationId);
print(e);
}
}
}
class VerifyPhoneNumberEvent extends AuthenticationEvent {
final String phoneNumber;
VerifyPhoneNumberEvent({this.phoneNumber});
}
class VerifySmsCodeEvent extends AuthenticationEvent {
final String smsCode;
VerifySmsCodeEvent({this.smsCode});
}
class PhoneCodeSentEvent extends AuthenticationEvent {}
class VerificationCompletedEvent extends AuthenticationEvent {
final FirebaseUser firebaseUser;
final bool isVerified;
VerificationCompletedEvent({@required this.firebaseUser, @required this.isVerified});
@override
List<Object> get props => [firebaseUser,isVerified];
@override
String toString() => 'VerificationCompleteEvent{user:${firebaseUser.displayName}, isVerified: $isVerified}';
}
class VerificationExceptionEvent extends AuthenticationEvent {
final String message;
VerificationExceptionEvent(this.message);
}
class PhoneCodeAutoRetrievalTimeoutEvent extends AuthenticationEvent {
final String verificationId;
PhoneCodeAutoRetrievalTimeoutEvent({@required this.verificationId});
@override
List<Object> get props => [verificationId];
@override
String toString() => 'PhoneCodeAutoRetrievalTimeoutEvent {verificationId: $verificationId}';
}
class OtpSentState extends AuthenticationState {}
class VerifyingState extends AuthenticationState {}
class OtpVerifiedState extends AuthenticationState {}
class PhoneCodeAutoRetrievalTimeoutState extends AuthenticationState {
final String verificationId;
PhoneCodeAutoRetrievalTimeoutState({@required this.verificationId});
@override
List<Object> get props => [verificationId];
@override
String toString() => 'PhoneCodeAutoRetrievalTimeoutState {verificationId: $verificationId}';
}
class VerificationCompleteState extends AuthenticationState {
final FirebaseUser firebaseUser;
final bool isVerified;
VerificationCompleteState({@required this.firebaseUser, @required this.isVerified});
FirebaseUser getUser(){
return firebaseUser;
}
@override
List<Object> get props => [firebaseUser, isVerified];
@override
String toString() => 'VerificationCompleteState{user:${firebaseUser.displayName}, isVerified: $isVerified}';
}
class VerificationExceptionState extends AuthenticationState {
final String message;
VerificationExceptionState({this.message});
@override
// TODO: implement props
List<Object> get props => [message];
}
class OtpExceptionState extends AuthenticationState {
final String message;
final String verificationId;
OtpExceptionState({@required this.message, @required this.verificationId});
@override
// TODO: implement props
List<Object> get props => [message, verificationId];
}
答案 0 :(得分:0)
经过几次试验,我发现主要问题是使用AuthenticationBloc
,所以我在仍然使用PhoneAuthenticationBloc
和AuthenticationState
的同时制作了专用的AuthenticationState
并照顾了没有“ StreamController”的事件/状态路由。
我将把课程留在这里,以帮助其他人。
class PhoneAuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
final UserRepository _userRepository;
PhoneAuthenticationBloc({@required UserRepository userRepository})
: assert(userRepository != null),
_userRepository = userRepository;
String verificationId = "";
@override
AuthenticationState get initialState => Uninitialized();
@override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event) async* {
// phone verification
if (event is VerifyPhoneNumberEvent) {
print('VerifyPhoneNumberEvent received');
yield VerifyingState();
yield* _mapVerifyPhoneNumberToState(event);
}
if (event is PhoneCodeSentEvent) {
print('PhoneCodeSentEvent received');
yield OtpSentState();
}
if (event is VerificationCompletedEvent) {
print('VerificationCompletedEvent received');
yield VerificationCompleteState(firebaseUser: event.firebaseUser, isVerified: event.isVerified);
}
if (event is VerificationExceptionEvent) {
print('VerificationExceptionEvent received');
yield VerificationExceptionState(message: event.message);
}
if ( event is PhoneCodeAutoRetrievalTimeoutEvent){
yield PhoneCodeAutoRetrievalTimeoutState(verificationId: event.verificationId);
}
if(event is SendVerificationCodeEvent) {
yield VerifyingState();
yield*_mapVerificationCodeToState(event);
}
}
Stream<AuthenticationState> _mapVerifyPhoneNumberToState(VerifyPhoneNumberEvent event) async* {
print('_mapVerifyPhoneNumberToState V2 started');
final phoneVerificationCompleted = (AuthCredential authCredential) {
print('_mapVerifyPhoneNumberToState PhoneVerificationCompleted');
_userRepository.getCurrentUser().catchError((onError) {
print(onError);
}).then((user) {
add(VerificationCompletedEvent(firebaseUser: user, isVerified: true));
});
};
final phoneVerificationFailed = (AuthException authException) {
print('_mapVerifyPhoneNumberToState PhoneVerificationFailed');
print(authException.message);
add(VerificationExceptionEvent(onError.toString()));
};
final phoneCodeSent = (String verificationId, [int forceResent]) {
print('_mapVerifyPhoneNumberToState PhoneCodeSent');
this.verificationId = verificationId;
add(PhoneCodeSentEvent());
};
final phoneCodeAutoRetrievalTimeout = (String verificationId) {
print('_mapVerifyPhoneNumberToState PhoneCodeAutoRetrievalTimeout');
this.verificationId = verificationId;
add(PhoneCodeAutoRetrievalTimeoutEvent(verificationId: verificationId));
};
await _userRepository.verifyPhone(
phoneNumber: event.phoneNumber,
timeOut: Duration(seconds: 0),
phoneVerificationFailed: phoneVerificationFailed,
phoneVerificationCompleted: phoneVerificationCompleted,
phoneCodeSent: phoneCodeSent,
autoRetrievalTimeout: phoneCodeAutoRetrievalTimeout);
}
Stream<AuthenticationState> _mapVerificationCodeToState(SendVerificationCodeEvent event) async* {
print('_mapVerificationCodeToState started');
AuthResult result = await _userRepository.verifyAndLinkAuthCredentials(verificationId: verificationId, smsCode: event.smsCode)
.catchError((e){
print('verifyAndLinkAuthCredentials error: $e');
});
print(result);
if (result != null) {
yield VerificationCompleteState(firebaseUser: result.user, isVerified: true);
} else {
yield OtpExceptionState(message: "Invalid otp!", verificationId: verificationId);
}
}
}
Future<void> verifyPhone(
{@required String phoneNumber,
@required Duration timeOut,
@required PhoneVerificationFailed phoneVerificationFailed,
@required PhoneVerificationCompleted phoneVerificationCompleted,
@required PhoneCodeSent phoneCodeSent,
@required PhoneCodeAutoRetrievalTimeout autoRetrievalTimeout}) async {
_firebaseAuth.verifyPhoneNumber(
phoneNumber: phoneNumber,
timeout: timeOut,
verificationCompleted: phoneVerificationCompleted,
verificationFailed: phoneVerificationFailed,
codeSent: phoneCodeSent,
codeAutoRetrievalTimeout: autoRetrievalTimeout);
}
Future<AuthResult> verifyAndLinkAuthCredentials(
{@required String verificationId, @required String smsCode}) async {
AuthCredential authCredential = PhoneAuthProvider.getCredential(
verificationId: verificationId, smsCode: smsCode);
// return _firebaseAuth.signInWithCredential(authCredential);
FirebaseUser user = await _firebaseAuth.currentUser();
return user.linkWithCredential(authCredential).catchError((e) {
print('UserRepository.verifyAndLinkAuthCredentials() error: $e');
// return;
});
}
class VerifyPhoneNumberEvent extends AuthenticationEvent {
final String phoneNumber;
VerifyPhoneNumberEvent({this.phoneNumber});
}
class VerifySmsCodeEvent extends AuthenticationEvent {
final String smsCode;
VerifySmsCodeEvent({this.smsCode});
}
class PhoneCodeSentEvent extends AuthenticationEvent {}
class VerificationCompletedEvent extends AuthenticationEvent {
final FirebaseUser firebaseUser;
final bool isVerified;
VerificationCompletedEvent({@required this.firebaseUser, @required this.isVerified});
@override
List<Object> get props => [firebaseUser,isVerified];
@override
String toString() => 'VerificationCompleteEvent{user:${firebaseUser.displayName}, isVerified: $isVerified}';
}
class VerificationExceptionEvent extends AuthenticationEvent {
final String message;
VerificationExceptionEvent(this.message);
}
class PhoneCodeAutoRetrievalTimeoutEvent extends AuthenticationEvent {
final String verificationId;
PhoneCodeAutoRetrievalTimeoutEvent({@required this.verificationId});
@override
List<Object> get props => [verificationId];
@override
String toString() => 'PhoneCodeAutoRetrievalTimeoutEvent {verificationId: $verificationId}';
}
class OtpSentState extends AuthenticationState {}
class VerifyingState extends AuthenticationState {}
class OtpVerifiedState extends AuthenticationState {}
class PhoneCodeAutoRetrievalTimeoutState extends AuthenticationState {
final String verificationId;
PhoneCodeAutoRetrievalTimeoutState({@required this.verificationId});
@override
List<Object> get props => [verificationId];
@override
String toString() => 'PhoneCodeAutoRetrievalTimeoutState {verificationId: $verificationId}';
}
class VerificationCompleteState extends AuthenticationState {
final FirebaseUser firebaseUser;
final bool isVerified;
VerificationCompleteState({@required this.firebaseUser, @required this.isVerified});
FirebaseUser getUser(){
return firebaseUser;
}
@override
List<Object> get props => [firebaseUser, isVerified];
@override
String toString() => 'VerificationCompleteState{user:${firebaseUser.displayName}, isVerified: $isVerified}';
}
class VerificationExceptionState extends AuthenticationState {
final String message;
VerificationExceptionState({this.message});
@override
// TODO: implement props
List<Object> get props => [message];
}
class OtpExceptionState extends AuthenticationState {
final String message;
final String verificationId;
OtpExceptionState({@required this.message, @required this.verificationId});
@override
// TODO: implement props
List<Object> get props => [message, verificationId];
}
showDialog(
context: context,
barrierDismissible: false,
builder:
(BuildContext dialogContext) {
return SingleChildScrollView(
child: ValidatePhoneDialog(
controller: controller,
onPressed: () {
if (controller
.text.length >=
9) {
BlocProvider.of<
PhoneAuthenticationBloc>(
context)
.add(VerifyPhoneNumberEvent(
phoneNumber:
controller
.text
.replaceAll(
' ',
'')));
} else {
scaffoldKey.currentState
.showSnackBar(SnackBar(
backgroundColor:
Colors
.redAccent,
content: Text(
AppLocalizations
.instance
.text(
'Wrong number'),
style: TextStyle(
color: Colors
.white))));
}
}),
);
});
BlocListener<PhoneAuthenticationBloc, AuthenticationState>(
listener:
(BuildContext context, AuthenticationState state) {
if (state is VerifyingState) {
Navigator.of(context, rootNavigator: false).pop(context);
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return VerifyingDialog();
});
}
if (state is VerificationExceptionState) {
// ain't no sunshine
Navigator.of(context, rootNavigator: false).pop(context);
// Todo retry
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext dialogContext) {
return SingleChildScrollView(
child: ValidatePhoneRetryDialog(
controller: controller,
onPressed: () {
if (controller.text.length >= 9) {
BlocProvider.of<PhoneAuthenticationBloc>(
context)
.add(VerifyPhoneNumberEvent(
phoneNumber: controller.text
.replaceAll(' ', '')));
// Navigator.pop(context);
Navigator.of(context, rootNavigator: false)
.pop();
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return VerifyingDialog();
});
} else {
scaffoldKey.currentState.showSnackBar(
SnackBar(
backgroundColor: Colors.redAccent,
content: Text(
AppLocalizations.instance
.text('Wrong number'),
style: TextStyle(
color: Colors.white))));
}
}),
);
});
// scaffoldKey.currentState.showSnackBar(SnackBar(
// backgroundColor: Colors.redAccent,
// content: Text(
// AppLocalizations.instance
// .text('Phone verification error'),
// style: TextStyle(color: Colors.white))));
}
if (state is PhoneCodeAutoRetrievalTimeoutState) {
//manually insert OTP
print('PhoneCodeAutoRetrievalTimeoutState');
setState(() {
controller.text = '';
});
Navigator.of(context, rootNavigator: false).pop(context);
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext dialogContext) {
return SingleChildScrollView(
child: VerifyOtpDialog(
controller: controller,
onPressed: () {
if (controller.text.length == 6) {
BlocProvider.of<PhoneAuthenticationBloc>(
context)
.add(SendVerificationCodeEvent(
verificationId:
state.verificationId,
smsCode: controller.text
.replaceAll(' ', '')));
} else {
scaffoldKey.currentState.showSnackBar(
SnackBar(
backgroundColor: Colors.redAccent,
content: Text(
AppLocalizations.instance
.text('Wrong code'),
style: TextStyle(
color: Colors.white))));
}
}),
);
});
}
if (state is OtpExceptionState) {
// if at the first you don't succeed..
Navigator.of(context, rootNavigator: false).pop(context);
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext dialogContext) {
return SingleChildScrollView(
child: VerifyOtpRetryDialog(
controller: controller,
onPressed: () {
if (controller.text.length == 6) {
BlocProvider.of<PhoneAuthenticationBloc>(
context)
.add(SendVerificationCodeEvent(
verificationId:
state.verificationId,
smsCode: controller.text
.replaceAll(' ', '')));
} else {
scaffoldKey.currentState.showSnackBar(
SnackBar(
backgroundColor: Colors.redAccent,
content: Text(
AppLocalizations.instance
.text('Wrong code'),
style: TextStyle(
color: Colors.white))));
}
}),
);
});
}
if (state is VerificationCompleteState) {
// kool and the gang
if (state.isVerified == true) {
setState(() {
isVerified = state.isVerified;
});
Navigator.of(context, rootNavigator: false).pop(context);
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext dialogContext) {
return VerifiedPhoneConfirmationDialog();
});
BlocProvider.of<UserBloc>(context)
.add(UserPhoneVerified(user: widget.user));
Timer(Duration(milliseconds: 1200), () {
Navigator.of(context, rootNavigator: false).pop();
});
// TODO: Save user isVerified to LocalDb and Firebase
}
}
}),