Bad state: Migrate To flutter_bloc v8.0.1

Issue

I am trying to fix an issue related to Flutter Bloc. I am editing someone else code to make it work with the latest flutter_bloc version but I am unable to do so. Can someone do a rewrite for my code so I can run it? I saw many answers but I am unable to understand how to fix my own code.

This is the complete code for all_categories_bloc.dart

    class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
  AllCategoriesBloc({
    this.apiRepository,
  }) : super(AllCategoriesInitial()) {
    on<GetAllCategories>(_onGetAllCategories);
  }

  final ApiRepository apiRepository;

  Future<void> _onGetAllCategories(
    GetAllCategories event,
    Emitter<AllCategoriesState> emit,
  ) async {
    try {
      emit(const AllCategoriesLoading());

      final categoriesModel = await apiRepository.fetchCategoriesList();

      emit(AllCategoriesLoaded(categoriesModel));

      if (categoriesModel.error != null) {
        emit(AllCategoriesError(categoriesModel.error));
      }
    } catch (e) {
      emit(
        const AllCategoriesError(
          "Failed to fetch all categories data. Is your device online ?",
        ),
      );
    }
  }
}

Code for all_categories_event.dart

abstract class AllCategoriesEvent extends Equatable {
  AllCategoriesEvent();
}

class GetAllCategories extends AllCategoriesEvent {

  @override
  List<Object> get props => null;

}

Code for all_categories_state.dart

abstract class AllCategoriesState extends Equatable {
  const AllCategoriesState();
}

class AllCategoriesInitial extends AllCategoriesState {
  AllCategoriesInitial();

  @override
  List<Object> get props => [];
}

class AllCategoriesLoading extends AllCategoriesState {
  const AllCategoriesLoading();
  @override
  List<Object> get props => null;
}

class AllCategoriesLoaded extends AllCategoriesState {

  final CategoriesModel categoriesModel;
  const AllCategoriesLoaded(this.categoriesModel);

  @override
  List<Object> get props => [categoriesModel];
}

class AllCategoriesError extends AllCategoriesState {

  final String message;
  const AllCategoriesError(this.message);

  @override
  List<Object> get props => [message];
}

It throws an error "Bad state: add(GetAllCategories) was called without a registered event handler.
Make sure to register a handler via on((event, emit) {…})"

I have this add(GetAllCategories) in my home. dart file but the solution is to edit this code which I am unable to do so. Can someone do a rewrite for the latest bloc? I would be thankful.

Solution

Let’s get through the migration guide step by step:

  1. package:bloc v5.0.0: initialState has been removed. For more information check out #1304.

You should simply remove the AllCategoriesState get initialState => AllCategoriesInitial(); portion from your BLoC.

  1. package:bloc v7.2.0 Introduce new on<Event> API. For more information, read the full proposal.

As a part of this migration, the mapEventToState method was removed, each event is registered in the constructor separately with the on<Event> API.

First of all, register your events in the constructor:

AllCategoriesBloc() : super(AllCategoriesInitial()) {
  on<GetAllCategories>(_onGetAllCategories);
}

Then, create the _onGetAllCategories method:

Future<void> _onGetAllCategories(
  GetAllCategories event,
  Emitter<AllCategoriesState> emit,
) async {
  try {
    emit(const AllCategoriesLoading());

    final categoriesModel = await _apiRepository.fetchCategoriesList();

    emit(AllCategoriesLoaded(categoriesModel));

    if (categoriesModel.error != null) {
      emit(AllCategoriesError(categoriesModel.error));
    }
  } catch (e) {
    emit(
      const AllCategoriesError(
        "Failed to fetch all categories data. Is your device online ?",
      ),
    );
  }
}

Notice, that instead of using generators and yielding the next state, you should use the Emitter<AllCategoriesState> emitter.

Here is the final result of the migrated AllCategoriesBloc:

class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
  AllCategoriesBloc() : super(AllCategoriesInitial()) {
    on<GetAllCategories>(_onGetAllCategories);
  }

  final ApiRepository _apiRepository = ApiRepository();

  Future<void> _onGetAllCategories(
    GetAllCategories event,
    Emitter<AllCategoriesState> emit,
  ) async {
    try {
      emit(const AllCategoriesLoading());

      final categoriesModel = await _apiRepository.fetchCategoriesList();

      emit(AllCategoriesLoaded(categoriesModel));

      if (categoriesModel.error != null) {
        emit(AllCategoriesError(categoriesModel.error));
      }
    } catch (e) {
      emit(
        const AllCategoriesError(
          "Failed to fetch all categories data. Is your device online ?",
        ),
      );
    }
  }
}

Bonus tip

Instead of creating an instance of ApiRepository inside the BLoC directly, you can use the constructor injection:

class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
  AllCategoriesBloc({
    required this.apiRepository,
  }) : super(AllCategoriesInitial()) {
    on<GetAllCategories>(_onGetAllCategories);
  }

  final ApiRepository apiRepository;

  ...
}

Now, when creating BLoC, pass the instance of the repository to the constructor, like AllCategoriesBloc(apiRepository: ApiRepository()). This way you will be able to properly unit test your BLoC by mocking dependencies (in this case, ApiRepository).

Answered By – mkobuolys

Answer Checked By – Terry (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *