I have this code in user_bloc.dart:
UserBloc(this._userRepository, UserState userState) : super(userState) {
on<RegisterUser>(_onRegisterUser);
}
void _onRegisterUser(RegisterUser event, Emitter<UserState> emit) async {
emit(UserRegistering());
try {
// detect when the user is registered
FirebaseAuth.instance.authStateChanges().listen((User? user) async {
if (state is UserRegistering && user != null) {
// save user to db
try {
await _userRepository.addUpdateUserInfo(user.uid, userInfo);
} catch (e) {
emit(UserRegisteringError("Could not save user"));
}
emit(UserLoggedIn(user));
}
});
... call FirebaseAuth.instance.createUserWithEmailAndPassword
this is _userRepository.addUpdateUserInfo:
Future<void> addUpdateUserInfo(String userId, UserInfo userInfo) async {
try {
var doc = usersInfo.doc(userId);
await doc.set(
{
'first_name': userInfo.firstName,
'last_name': userInfo.lastName,
'email': userInfo.email
},
SetOptions(merge: true),
);
} catch (e) {
print("Failed to add user: $e");
throw Exception("Failed to add user");
}
}
When emit(UserLoggedIn(user));
is called I get this error:
Error: Assertion failed:
..\…\src\emitter.dart:114
!_isCompleted
"\n\n\nemit was called after an event handler completed normally.\nThis is usually due to an unawaited future in an event handler.\nPlease make sure to await all asynchronous operations with event handlers\nand use emit.isDone after asynchronous operations before calling emit() to\nensure the event handler has not completed.\n\n **BAD**\n on<Event>((event, emit) {\n future.whenComplete(() => emit(...));\n });\n\n **GOOD**\n on<Event>((event, emit) async {\n await future.whenComplete(() => emit(...));\n });\n"
at Object.throw_ [as throw] (http://localhost:58334/dart_sdk.js:5067:11)
at Object.assertFailed (http://localhost:58334/dart_sdk.js:4992:15)
at _Emitter.new.call (http://localhost:58334/packages/bloc/src/transition.dart.lib.js:765:40)
at user_bloc.UserBloc.new.<anonymous> (http://localhost:58334/packages/soli/blocs/bloc/user_bloc.dart.lib.js:90:20)
at Generator.next (<anonymous>)
at http://localhost:58334/dart_sdk.js:40571:33
at _RootZone.runUnary (http://localhost:58334/dart_sdk.js:40441:59)
at _FutureListener.thenAwait.handleValue (http://localhost:58334/dart_sdk.js:35363:29)
at handleValueCallback (http://localhost:58334/dart_sdk.js:35931:49)
at Function._propagateToListeners (http://localhost:58334/dart_sdk.js:35969:17)
at _Future.new.[_completeWithValue] (http://localhost:58334/dart_sdk.js:35817:23)
at async._AsyncCallbackEntry.new.callback (http://localhost:58334/dart_sdk.js:35838:35)
at Object._microtaskLoop (http://localhost:58334/dart_sdk.js:40708:13)
at _startMicrotaskLoop (http://localhost:58334/dart_sdk.js:40714:13)
at http://localhost:58334/dart_sdk.js:36191:9
[2022-02-17T03:47:00.057Z] @firebase/firestore:
After I get the error, if I try again with a different user, it works OK.
CodePudding user response:
This is the expected outcome since the _onRegisterUser
method has already finished executing but the code inside FirebaseAuth.instance.authStateChanges().listen(...)
is trying to emit state changes afterwards.
What you can do in this case, instead of emitting new states inside the FirebaseAuth listener, you should add new events to the BLoC:
if (state is UserRegistering && user != null) {
// save user to db
try {
await _userRepository.addUpdateUserInfo(user.uid, userInfo);
} catch (e) {
add(RegistrationErrorEvent()); // Create this event
}
add(UserLoggedInEvent(user: user)); // Create this event
}
Then, you should register these events and handle the logic:
UserBloc(this._userRepository, UserState userState) : super(userState) {
on<RegisterUser>(_onRegisterUser);
on<RegistrationErrorEvent>((event, emit) => emit(UserRegisteringError("Could not save user")));
on<UserLoggedInEvent>((event, emit) => emit(UserLoggedIn(event.user)));
}
Also, since you are using streams and the listen
method, consider using StreamSubscription
so you could clean up the resources after listening for the state changes.
Here is an example from the bloc repository on GitHub.