Flutter使用flutter_bloc出现emit was called after an event handler completed报错
flutter_bloc emit调用异常排查
问题背景
使用Django REST Framework开发后端API时,接口已验证可正常返回响应数据,但Flutter端调试控制台抛出flutter_bloc相关异常,报错指向登录事件处理方法内的第二个emit调用位置。
错误提示
emit was called after an event handler completed normally.
This is usually due to an unawaited future in an event handler.
根本原因
注册LoginFormEvent的处理函数时,外层回调没有添加async关键字,即使内部loggedIn事件的分支回调加了async,外层回调也不会等待内部异步操作执行完成,会直接结束并关闭emit通道。等_getAuthInfo异步请求完成后再调用第二个emit时,emit通道已经被标记为完成,因此触发断言错误。
修复方案
方案1:补全async/await关键字
给外层事件回调添加async关键字,同时对event.map的返回值做await处理,确保所有异步逻辑执行完成后再结束事件处理:
class LoginFormBloc extends Bloc<LoginFormEvent, LoginFormState> { LoginFormBloc(this._getAuthInfo) : super(LoginFormState.initial()) { on<LoginFormEvent>( // 外层回调添加async关键字 (event, emit) async { // 等待event.map所有分支逻辑执行完成 await event.map( emailChanged: (e) async { emit( state.copyWith( emailAddress: EmailAddress(e.emailStr), authFailureOrSuccessOption: none(), ), ); }, passwordChanged: (e) async { emit( state.copyWith( password: Password(e.passwordStr), authFailureOrSuccessOption: none(), ), ); }, loggedIn: (e) async { final isEmailValid = state.emailAddress.value.isRight(); final isPasswordValid = state.password.value.isRight(); Either<AuthFailure, TokenValue>? loginFailureOrSuccess; if (isEmailValid && isPasswordValid) { emit( state.copyWith( isSubmitting: true, authFailureOrSuccessOption: none(), ), ); loginFailureOrSuccess = await _getAuthInfo( Params( emailAddress: state.emailAddress, password: state.password, ), ); } emit( state.copyWith( isSubmitting: false, showErrorMessages: true, authFailureOrSuccessOption: optionOf(loginFailureOrSuccess), ), ); }, ); }, ); } final GetAuthToken _getAuthInfo; }
方案2:拆分事件处理逻辑(推荐,符合Bloc最佳实践)
将不同类型的事件拆分注册,每个事件对应独立的处理函数,避免嵌套异步逻辑导致的生命周期问题:
class LoginFormBloc extends Bloc<LoginFormEvent, LoginFormState> { LoginFormBloc(this._getAuthInfo) : super(LoginFormState.initial()) { // 单独注册每个事件的处理函数 on<_EmailChanged>(_handleEmailChanged); on<_PasswordChanged>(_handlePasswordChanged); on<_LoggedIn>(_handleLoggedIn); } final GetAuthToken _getAuthInfo; Future<void> _handleEmailChanged(_EmailChanged event, Emitter<LoginFormState> emit) { emit( state.copyWith( emailAddress: EmailAddress(event.emailStr), authFailureOrSuccessOption: none(), ), ); } Future<void> _handlePasswordChanged(_PasswordChanged event, Emitter<LoginFormState> emit) { emit( state.copyWith( password: Password(event.passwordStr), authFailureOrSuccessOption: none(), ), ); } Future<void> _handleLoggedIn(_LoggedIn event, Emitter<LoginFormState> emit) async { final isEmailValid = state.emailAddress.value.isRight(); final isPasswordValid = state.password.value.isRight(); Either<AuthFailure, TokenValue>? loginFailureOrSuccess; if (isEmailValid && isPasswordValid) { emit( state.copyWith( isSubmitting: true, authFailureOrSuccessOption: none(), ), ); loginFailureOrSuccess = await _getAuthInfo( Params( emailAddress: state.emailAddress, password: state.password, ), ); } emit( state.copyWith( isSubmitting: false, showErrorMessages: true, authFailureOrSuccessOption: optionOf(loginFailureOrSuccess), ), ); } }
注意事项
Bloc 7.2及以上版本对emit的生命周期做了严格限制:事件处理函数执行完成后,emit通道会被立即标记为已完成,后续所有emit调用都会触发该断言错误。所有事件处理函数内的异步操作必须添加await关键字,确保emit调用都发生在通道关闭之前。
内容的提问来源于stack exchange,提问作者Biplab Dutta
相关产品推荐
相关产品推荐

