How to wait multiple bloc events in same bloc

Viewed 4155

I have one bloc with multiple events. Here I load categories and locations and wait using BlocListener. But my condition for show circular progress indicator work incorrectly and after load categories and locations also shows. How I can use bloc correctly in this case?

Code

apiDataBloc.add(LoadCategoriesEvent());
apiDataBloc.add(LoadLocationsEvent());

------------------------

return BlocListener<ApiDataBloc, ApiDataState>(
  listener: (context, state) {
    if (state is CategoriesLoaded) {
      categories = state.categories;
      print("Categories loaded");
      print(categories.length);
    }
  },
  child: BlocListener<ApiDataBloc, ApiDataState>(
    listener: (context, s) {
      if (s is LocationsLoaded) {
        locations = s.locations;
        print("Locations loaded");
        print(locations.length);
      }
    },
    child: locations != null &&
            categories != null &&
            categories.length > 0 &&
            locations.length > 0
        ? Container(child: Center(child: Text('Categories and locations loaded!')))
        : Container(child: Center(child: CircularProgressIndicator())),
  ),
);

I tried also like this but doesn't work.

return BlocProvider<ApiDataBloc>(
      create: (context) => apiDataBloc,
      child: BlocBuilder<ApiDataBloc, ApiDataState>(
        builder: (context, state) {
          if (state is LocationsLoaded) {
            print("Locations loaded");
            locations = state.locations;
            print(locations.length);
            return BlocBuilder<ApiDataBloc, ApiDataState>(
              builder: (context, s) {
                if (s is CategoriesLoaded) {
                  print("Categories loaded");
                  categories = s.categories;
                  print(categories.length);
                  return Container(
                      child: Center(
                          child: Text('Categories and locations loaded!')));
                }
                return Container(
                    child: Center(child: CircularProgressIndicator()));
              },
            );
          }
          return Container(child: Center(child: CircularProgressIndicator()));
        },
      ),
    );
3 Answers

I would place the logic into the bloc. If I understand correctly, you get an event triggered as soon as the data is loaded. Then you could create 2 variables in the bloc bool categoriesLoaded, locationsLoaded which you set true upon the event. In mapEventToState you could forward from each of those event mappers to a common event mapper that checks if both variables are true and sends the proper state then. An inProgress state could display which of the data streams has already been loaded.

You should create one state DataLoaded with 2 fields categories and locations

Something like that:

class DataLoaded extends ApiDataState {

  const DataLoaded(
    this.categories,
    this.locations,
  );

  final List<Type> categories;
  final List<Type> locations;

  @override
  String toString() => 'DataLoaded';
}

Then you need to fetch data from API in the ApiDataBloc class:

class ApiDataBloc extends Bloc<YourEventType, ApiDataState> {
  ApiDataBloc() : super(YourInitialState());

  @override
  Stream<ApiDataState> mapEventToState(YourEventType event) async* {
    if (event is YourFetchApiEvent) {
      yield YourLoadingState();

      final categories = await _fetchCategories();
      final locations = await _fetchLocations();

      yield DataLoaded(categories,locations);
    }
   }
 }

and the final step is BlocBuilder in your widget:

return BlocProvider<ApiDataBloc>(
      create: (context) => apiDataBloc,
      child: BlocBuilder<ApiDataBloc, ApiDataState>(
        builder: (context, state) {
          if (state is YouLoadingState) {
            return Center(child: CircularProgressIndicator());
          }
          if (state is DataLoaded) {
            print(state.locations);
            print(state.categories);
            return Center(
             child: Text('Categories and locations loaded!'),
            );
          }
        },
      ),
    );

I know what you meant.

Example Case:
#some_bloc.dart (not in event or state file)
on<someEventNo1>((......) =>
   emit(LoadingState());
   emit(EmitResultAPI());

on<someEventNo2>((......) =>
   emit(LoadingState());
   emit(someState());

#main.dart
someMethod() {
   BlocProvider.of<SomeBloc>(context).add(someEventNo1());
   BlocProvider.of<SomeBloc>(context).add(someEventNo2());
}

If you do your code like that, bloc builder will not catch state change when someEventNo1 emits EmitResultAPI, because you are sending 2 consecutive BlocProvider.of<>().

Solution:

BlocProvider.of<SomeBloc>(context).add(someEventNo1());
Future.delayed(Duration(miliseconds: 100)).then((valueFuture) => BlocProvider.of<SomeBloc>(context).add(someEventNo2()));
Related