'emit was called after an event handler completed normally' issue inside a periodic Timer

Issue

Im using Flutter and flutter_bloc to make an app where it periodically sends an API request to my server and requests data.
It was working perfectly at first before i implemented the periodic functionality using Timer, this is my bloc file:

class HomeBeehivesBloc extends Bloc<HomeBeehivesEvent, HomeBeehivesState> {
  HomeBeehivesBloc() : super(BeehivesInitial()) {
    on<LoadBeehives>((event, emit) => _onBeehivesLoaded(emit));
  }

  Future<void> _onBeehivesLoaded(Emitter<HomeBeehivesState> emit) async {
    emit(BeehivesLoading());

    final repository = BeehivesRepository();

    const duration = Duration(seconds: 5);
    Timer.periodic(duration, (timer) async {
      try {
        await repository.getHomeBeehives().then((beehives) async {
          emit(BeehivesLoadedSuccessfully(beehives: beehives));
        });
      } catch (exception) {
        log(exception.toString());
      }
    });
  }
}

But im getting this exception:

'package:bloc/src/emitter.dart': Failed assertion: line 114 pos 7: '!_isCompleted': 

      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>((event, emit) {
          future.whenComplete(() => emit(...));
        });
      
        **GOOD**
        on<Event>((event, emit) async {
          await future.whenComplete(() => emit(...));
        });

Ive tried searching everywhere for a solution, but honestly this is the first time i used the new version of the bloc package (never used emit before), and i would like some suggestion as to how to solve this issue.

Side question: Is it a good idea to implement the periodic timer there ? because i have seen some people implement it within the frontend (Stateful widget for example), i would also love any suggestions about this.

Thank you very much!

Solution

Your code does not wait for the callback inside Timer.periodic to complete – the _onBeehivesLoaded method finishes executing, hence when the callback tries to emit a new state (BeehivesLoadedSuccessfully), you get this error.

To resolve this, instead of emitting a new state inside the callback, you should add a new event to the BLoC and handle it later as any other BLoC event.

  1. First of all, create a new event, like HomeBeehivesLoaded:
class HomeBeehivesLoaded extends HomeBeehivesEvent {
  final List<Beehive> beehives; // <-- Not sure if Beehive is the right type, just an assumption

  const HomeBeehivesLoaded(this.beehives);
}
  1. Register a new event handler in your BLoC that will update the state based on the loaded behives:
class HomeBeehivesBloc extends Bloc<HomeBeehivesEvent, HomeBeehivesState> {
  HomeBeehivesBloc() : super(BeehivesInitial()) {
    <...>
    on<HomeBeehivesLoaded>(_onHomeBeehivesLoaded);
  }
  
  void _onHomeBeehivesLoaded(HomeBeehivesLoaded event, Emitter<HomeBeehivesState> emit) {
    emit(BeehivesLoadedSuccessfully(beehives: event.beehives));
  }

  <...>
}
  1. Inside the Timer.periodic callback, add this event once you get the response from the repository instead of emitting a state:
class HomeBeehivesBloc extends Bloc<HomeBeehivesEvent, HomeBeehivesState> {
  <...>

  Future<void> _onBeehivesLoaded(Emitter<HomeBeehivesState> emit) async {
    <...>

    Timer.periodic(duration, (timer) async {
      try {
        await repository.getHomeBeehives().then((beehives) async {
          add(HomeBeehivesLoaded(beehives: beehives));
        });
      } catch (exception) {
        log(exception.toString());
      }
    });
  }
}

Answered By – mkobuolys

Answer Checked By – Willingham (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published.