1

I am trying to find solution to manage async queries. For example, i have internet shop and i want to update all products and categories when city is changed. And i don't want to keep all async logic on ui. In order to achieve this result, i've created this bloc:

class AppEvent {
  String message;

  AppEvent({this.message = ''});
}

class EventsBlock extends Bloc<AppEvent, AppEvent> {
  EventsBlock() : super(AppEvent());
 
  @override
  Stream<AppEvent> mapEventToState(AppEvent event) async* {
    yield event;
  }
}

final events = EventsBlock();

Then, i can use it like this:

class CityCubit() {
  CityCubit() : super(CityState());  
  
  Future<void> changeCity() async {
    await api.changeCity();
    events.add(AppEvent(message: 'cityChanged'));   
  }
}

class CategoryCubit extends Cubit<CategoryState> {
  CategoryCubit() : super(CategoryEmptyState()) {
    events.stream.listen((e) {
        if(e.message == 'cityChanged') {
            fetchCategories();
        }
      });
  };

  Future<void> fetchCategories() async {
    //fetch categories
  }
}

class ProductCubit extends Cubit<ProductState> {
  ProductCubit() : super(ProductEmptyState()) {
    events.stream.listen((e) {
        if(e.message == 'cityChanged') {
            fetchProducts();
        }
      });
  };

  Future<void> fetchProducts() async {
    //fetch products
  }
}

It's something like eventBus pattern. But i am not sure that it's a correct way to use bloc. I've tried to use redux + redux saga, but it has a lot of boilerplate and i believe that flutter has better solution to manage things like that.

Sergey Zhukov
  • 649
  • 5
  • 7

2 Answers2

1

Your general idea is ok, but I can't see a real need for the EventsBloc class. In fact, it is kinda strange that you use the same class for the events and for the states of this bloc, and simply yield the event you receive. It's like EventsBloc could be a simple stream.

Here's a way to go, turning CityCubit into an actual bloc (and also with some error handling, which is something you can do gracefully with bloc):

abstract class CityState {}
class CityInitial extends CityState {}
class CityLoadingCities extends CityState {}
class CityCitiesLoaded extends CityState {}
class CityLoadError extends CityState {}

abstract class CityEvent {}
class CityLoadCities extends CityEvent {}

class CityBloc<CityEvent, CityState> {
  CityBloc() : super(CityInitial());  

  @override
  Stream<AppEvent> mapEventToState(CityEvent event) async* {
    if(event is CityLoadCities) {
      yield CityLoadingCities();
      try {
        await api.changeCity();
        yield CityCitiesLoaded();
      } catch (error) {
        yield CityLoadError();
      }
    }
  }
  
  void changeCity() {
    add(CityLoadCities());   
  }
}

Now you can do this inside any other bloc:

instanceOfCityBloc.listen((cityState) {
  if(cityState is CityCitiesLoaded){
    // do stuff
  }
});
Josenaldo
  • 63
  • 7
1

I ended up with this code:

class CityChangedEvent {
  int cityId;

  CityChangedEvent(this.cityId);
}

EventBus eventBus = EventBus();

mixin EventBusMixin {
  StreamSubscription<T> listenEvent<T>(void Function(T) subscription) =>
      eventBus.on<T>().listen(subscription);

  void shareEvent<S>(S event) => eventBus.fire(event);
}

class CityCubit extends CityCubit<CityState> with EventBusMixin {
  CityCubit() : super(CityInitialState());        

  Future<void> changeCity(cityId) async {
    try {
      emit(ChangeCityLoadingState());
      final result = await api.changeCity(cityId);
      if(result.success) {
        emit(ChangeCitySuccessState());
        shareEvent(CityChangedEvent(cityId));   
      }
    } catch (_) {
      emit(ChangeCityErrorState());
    }
  }
}

class CategoryCubit extends Cubit<CategoryState> with EventBusMixin {
  CategoryCubit() : super(CategoryEmptyState()) {
     listenEvent<CityChangedEvent>((e) {
        fetchCategories(e.cityId);
     );
  }

  Future<void> fetchCategories(cityId) async {
    try {
      emit(CategoryLoadingState());
      final categoriesList = await fetchCategoriesApi();
      emit(CategoryLoadedState(categories: categoriesList));
    } catch (_) {
      emit(CategoryErrorState());
    }
  }
}

Now, i can communicate between blocs without the need to instantiate or inject their instances. Thanks to this library https://pub.dev/packages/event_bus

Sergey Zhukov
  • 649
  • 5
  • 7