fluttersocket.iobloc

Bloc throws an exception "emit was called after an event handler completed normally."


I am using bloc and socket.io in my flutter application to react to login states from the server.

Future<void> _setLoginListener(SetLoginListener event, Emitter<LoginState> emit)async{
final socketClient = SocketClient.instance.socket;

try {
  socketClient?.on('loginRequestHandler',(data){
    if (data == null) {
       emit(LoginError("Email or Password wrong!"));
    } else {
       emit(LoginSuccessfully("Login granted"));
       CurrentUser currentUser = CurrentUser.fromJson(data);
    }
  });
} catch(e) {
   emit(LoginError(e.toString()));
}

I understand the exception, that emit() is called, but the future is not "done" yet, but I don't understand how to handle a future in this case.

There is no way to use an await socketClient?.on()... or an await emit().

I found some post on Stackoverflow to solve the error, but they are about different code situations.

I am thankful for every help!


Solution

  • As @pskink pointed out in the comment, when an event is completely handled, no emits are allowed, so if you want to still emit a new state you will have to create another event that emits the state that you want and trigger it where you currently emitting the new state, then the issue will go away!

    void _setLoginListener(SetLoginListener event, Emitter<LoginState> emit) {
        final socketClient = SocketClient.instance.socket;
    
        try {
            socketClient?.on('loginRequestHandler', (data) {
                if (data == null) {
                    // Removed the emit and used the add instead
                    add(LoginFailed());
                } else {
                    // Removed the emit and used the add instead
                    add(LoginSucceeded(data: data)); // Don't forget to pass the data to the new event
                }
            });
        } catch (e) {
            // Might need to be moved to another event, maybe call it SocketFailure
            emit(LoginError(e.toString()));
        }
    }
    
    void _onLoginFailed(LoginFailed event, Emitter<LoginState> emit) {
        emit(LoginError("Email or Password wrong!"));
    }
    
    void _onLoginSucceeded(LoginSucceeded event, Emitter<LoginState> emit) {
        emit(LoginSuccessfully("Login granted"));
        CurrentUser currentUser = CurrentUser.fromJson(event.data); // Here you can use the data passed to the event
    }
    

    And the events would look something like

    class LoginFailed extends ... {
        const LoginFailed();
    }
    
    class LoginSucceeded extends ... {
        final data;
    
        const LoginSucceeded({required this.data});
    }