Search code examples
flutterurl-rewritingblocflutter-bloc

Bad state: Migrate To flutter_bloc v8.0.1


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).