How to resolve emit was called after an event handler completed normally bloc error?
Asked Answered
S

5

20

I am using flutter bloc to make download progress percentage displayed but I keep getting this problem. I figured the problem arises in the onDone method but I couldn't figure out how to fix it.

ERROR :

Exception has occurred. _AssertionError ('package:bloc/src/bloc.dart': Failed assertion: line 137 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(...));
  });
)

CODE :


import 'package:bloc/bloc.dart';
import 'package:download_progress_with_bloc/downlaod_file.dart';
import 'package:download_progress_with_bloc/download_event.dart';
import 'package:download_progress_with_bloc/download_state.dart';
import 'package:download_progress_with_bloc/permission_handler.dart';
import 'package:download_progress_with_bloc/store_book_repo.dart';
import 'package:http/http.dart' as http;

class DownloadBloc extends Bloc<DownloadEvent, DownloadState> {
  DownloadBloc({required this.storeBookRepo}) : super(DownloadInitial()) {
    on<DownloadStarted>(onStarted);
    on<DownloadProgressed>(onProgressed);
  }
  final StoreBookRepo storeBookRepo;

  http.StreamedResponse? response;
  // StreamController? _controller;
  int received = 0;
  List<int> bytes = [];
  int totalSize = 0;

  @override
  Future<void> close() {
    return super.close();
  }

  Future<void> onStarted(
      DownloadStarted event, Emitter<DownloadState> emit) async {
    try {
      await PermissionHandler.requestStoragePermission();
      response = await downloadFile();
      totalSize = response!.contentLength ?? 0;
      emit(DownloadInProgress(progress: received, totalSize: totalSize));
      response?.stream.asBroadcastStream().listen((value) async {
        received += value.length;
        bytes.addAll(value);
        add(DownloadProgressed(progress: received));
        print('received value is $received');
      }).onDone(
        () async {
          await storeBookRepo
              .storePdf(
                bytes.toString(),
                bookTitle: 'bookTitle',
              )
              .then((value) => emit(DownloadCompleted()));
          // emit(DownloadCompleted());
        },
      );
    } catch (e) {
      emit(DownlaodFailed(errorMessage: '$e'));
    }
  }

  void onProgressed(DownloadProgressed event, Emitter<DownloadState> emit) {
    emit(DownloadInProgress(progress: event.progress, totalSize: totalSize));
  }


}
Smite answered 13/10, 2021 at 22:13 Comment(0)
B
13

What if rewrite listen to await for like this?

Future<void> onStarted(
  DownloadStarted event,
  Emitter<DownloadState> emit,
) async {
  try {
    await PermissionHandler.requestStoragePermission();
    response = await downloadFile();
    totalSize = response!.contentLength ?? 0;

    emit(DownloadInProgress(
      progress: received,
      totalSize: totalSize,
    ));

    await for (final value in response?.stream) {
      received += value.length;
      bytes.addAll(value);

      add(DownloadProgressed(progress: received));
      print('received value is $received');
    }

    await storeBookRepo.storePdf(
      bytes.toString(),
      bookTitle: 'bookTitle',
    );

    emit(DownloadCompleted());
  } catch (e) {
    emit(DownlaodFailed(errorMessage: '$e'));
  }
}
Bydgoszcz answered 8/11, 2021 at 20:19 Comment(0)
O
5

use async before on bloc and await for Future operations

on<NumberTriviaEvent>((event, emit) async {
   await Future func(){
     //some time consuming operation like fetch data
   }
   emit(YourState())  
}
Opsis answered 22/5, 2022 at 10:43 Comment(0)
W
5

I have this error too

In my case, when using Flutter_Bloc with Freezed If you used the event.when() etc. you must await the event.when like this and add async in each event when you use await

    Future<void> _onAddSection(
    SectionEvent event,
    Emitter<SectionState> emit,
  ) async {
    await event.whenOrNull(
      addSection: (sectionViewModel, chapterName) async {
        emit(const SectionState.loading());
        final section =
            _sectionMapper.convert<SectionViewModel, Section>(sectionViewModel);
        final result =
            await _sectionRepository.addSection(section, chapterName);
        result.fold(
          (failure) => emit(SectionState.failure(message: failure.message)),
          (data) => emit(SectionState.success(message: data)),
        );
      },
    );
  }
Wigley answered 10/5, 2023 at 16:3 Comment(0)
H
1

you should use async and await , its clear. but why? before complete run of each on() method, you can use emiteer. after that bloc will dispose or cancel that emitter.

Hula answered 23/10, 2022 at 4:38 Comment(0)
E
1

The above problem could be resolved by using either of the 2 approaches:

  1. By using the emit.forEach method of the bloc like below:

    Future<void> _onLoadSomething(LoadSomething event, 
    Emitter<SomethingState> emit) async {
    emit(YourLoadingState());
    await emit.forEach<YourReturnType>(
      _yourRepository.getSomething(),
      onData: YourLoadedState.new,
      onError: (error, stackTrace) => YourOperationFailure(error.toString()),
    );
    

    }

  2. or by using async for loop in a try and catch block like below:

    try{  
      //rest of the code
      await for (final value in response?.stream) {
      received += value.length;
      bytes.addAll(value);
      add(DownloadProgressed(progress: received));
      print('received value is $received');
      //rest of the code
      }
      }catch(error){
      emit(DownlaodFailed(errorMessage: '$error'));
      }
    
Eskridge answered 3/7, 2024 at 20:6 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.