Flutter Bloc Error : emit was called after an event handler completed normally - between two functions

6.1k Views Asked by At

I have the following problem...

emit was called after an event handler completed normally. This is usually due to an unawaited future in an event handler. Please make sure to await all asynchronous operations with event handlers and use emit.isDone after asynchronous operations before calling emit() to ensure the event handler has not completed.

BAD on((event, emit) { future.whenComplete(() => emit(...)); });

GOOD on((event, emit) async { await future.whenComplete(() => emit(...)); }); )

What happens is that in a function called _onLogIn, if the user has changed the language, it goes from there to another function inside the bloc, these two functions do not depend on each other, I mean that each function is called in different pages of the application, but still _onLogIn checks the _onChangeLanguage function.

  UserBloc({this.usecases}) : super(UserInitial()) {
    on<LogInEvent>(_onLogIn);
    on<ChangeLanguageEvent>(_onChangeLanguage);
  }

_onLogIn function :

void _onLogIn(
    LogInEvent event,
    Emitter<StateA> emit,
  ) async {

    emit(UserLoading());

    final userOrFailure = await services.logIn(
      x: event.x,
      y: event.y,
    );

    await userOrFailure.fold((user) async {

        /// If the user is logging in for the first time and does not
        /// have a preferred language.
        if (user.preferredLanguage == null) {
          emit(UserSuccess());

          emit(UserAlreadyLogged(connectedUser));

        } else {
          /// An ChangeLanguageEvent object
          ChangeLanguageEvent event = ChangeLanguageEvent(
            user.preferredLanguage,
            user.someId,
          );

          /// Call the other function in the same bloc
          this._onChangeLanguage(
            event,
            emit,
            isFromLogin: true,
          );
        }
      
    }, (failure) {
      emit(UserError(failure.message));
    });
  }

_onChangeLanguage function :

  void _onChangeLanguage(
    ChangeLanguageEvent event,
    Emitter<StateA> emit, {
    bool isFromLogin = false,
  }) async {

    final successOrFailure = await services.updateLanguage(
      event.language,
      event.someId,
    );

    await successOrFailure.fold( // ! HERE THE ERROR WHEN I LOG IN; but when i changed the language from the application i don't have an error
      (language) async {

        emit(ChangeAppLanguage(language));

        final sessionOrFailure = await services.getSession();

        sessionOrFailure.fold(
          (session) {
            /// I need this condition to know if the language comes from login
            if (isFromLogin) {
              emit(UserSuccess());
            }
            emit(UserAlreadyLogged(session));
          },
          (failure) => emit(UserError(failure.message)),
        );
      },
      (failure) {
        emit(UserError(failure.message));
      },
    );
  }

Any idea why? Thank you

3

There are 3 best solutions below

1
nvoigt On BEST ANSWER
void _onChangeLanguage(
    ChangeLanguageEvent event,
    Emitter<StateA> emit, {
    bool isFromLogin = false,
  }) async

This should be a major red flag. A call marked as async, but not returning a Future<>. There is no way, the caller could possibly await this call. Or even know that they should await this call.

Make it return a proper Future<void> instead of just void and your bloc should pick up on that and properly await the call.

There even is a linter rule for this: avoid_void_async. Did you turn off your linter? Don't do that. Turn your linter on and listen to it. Your other function has the same problem.

0
neronovs On

In my case I had to return Future<...>, but have not done it in the 'switch' statement, rather I've used the 'break' on all cases. So a compiler have not pointed me the lack of the 'return' statement. After I have put 'return' on all cases, the error disappeared.

0
Buhaescu Vlad On

Also for other people landing here, if the code inside _handleDigit method is inside the event.event.map.

Future<void> _onKeyPress(
    Emitter<PincodePageState> emit, _KeyPressPincodePageEvent event) async {
  var digitToParse = '';

  event.event.map(
      digit: (digit) async {
        digitToParse = digit.value;
      },
      delete: (delete) async {},
      biometry: (biometry) {},
      empty: (empty) {});

  await _handleDigit(emit, digitToParse);
}

Future<void> _handleDigit(Emitter<PincodePageState> emit, String digit) async {
  // some async process
}