You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 14:24:00