Manage Global Events by bloc - flutter

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.

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
}
});

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

Related

Unit Test Concrete Method of Abstract Class in Dart

I have an abstract class written in Dart, that not only contains abstract method but also contains concrete methods like below:
abstract class Person {
void speak();
Foot getFoot();
void walk() {
getFoot().move();
}
}
class Foot {
void move() {
}
}
Test:
class MockPerson extends Person {
#override
Foot getFoot() {
return Foot();
}
#override
void speak() {
}
}
void main() {
late MockPerson sut;
setUp(() {
sut = MockPerson();
});
test('test walk', () {
/// ??
});
}
I tried to created mock of Person to be able to test against it. But I'm not sure how can I write unit test for walk() method?

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.
Let's get through the migration guide step by step:
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.
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).

How to access Flutter BLoC state value in the new version?

I am new in this state management world. So I was trying to follow this tutorial (How to Save Products in a Wishlist using the BloC Pattern - EP10 - The eCommerce Series), but mapEventToState is deprecated so I am not sure what to do.
Here is my state:
part of 'wishlist_bloc.dart';
abstract class WishlistState extends Equatable {
const WishlistState();
#override
List<Object> get props => [];
}
class WishlistLoading extends WishlistState {}
class WishlistLoaded extends WishlistState {
final WishlistModel wishlist;
const WishlistLoaded({this.wishlist = const WishlistModel()});
#override
List<Object> get props => [wishlist];
}
class WishlistError extends WishlistState {}
Here is my event:
part of 'wishlist_bloc.dart';
abstract class WishlistEvent extends Equatable {
const WishlistEvent();
#override
List<Object> get props => [];
}
class StartWishlist extends WishlistEvent {}
class AddWishlistProduct extends WishlistEvent {
final ProductModel product;
const AddWishlistProduct(this.product);
#override
List<Object> get props => [product];
}
class RemoveWishlistProduct extends WishlistEvent {
final ProductModel product;
const RemoveWishlistProduct(this.product);
#override
List<Object> get props => [product];
}
Here is my bloc:
import '../models/product_model.dart';
import '../models/wishlist_model.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
part 'wishlist_event.dart';
part 'wishlist_state.dart';
class WishlistBloc extends Bloc<WishlistEvent, WishlistState> {
WishlistBloc() : super(WishlistLoading()) {
on<StartWishlist>(_mapStartWishlistToState);
on<AddWishlistProduct>(_mapAddWishlistToState);
on<RemoveWishlistProduct>(_mapRemoveWishlistToState);
}
void _mapStartWishlistToState(event, emit) async {
emit(WishlistLoading());
try {
await Future.delayed(Duration(seconds: 1));
emit(WishlistLoaded());
} catch (_) {}
}
// Error ...
void _mapAddWishlistToState(event, emit) async {
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from(state.wishlist.products)
..add(event.product))));
} catch (_) {}
}
}
void _mapRemoveWishlistToState(event, emit) async {}
}
But I get this error instead: "The getter 'wishlist' isn't defined for the type 'WishlistState'. Try importing the library that defines 'wishlist', correcting the name to the name of an existing getter, or defining a getter or field name 'wishlist'".
How to access 'wishlist' in the new version of flutter_bloc? Thank you.
Try to define arguments types while difining the functions. So your updated code would look like this:
import '../models/product_model.dart';
import '../models/wishlist_model.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
part 'wishlist_event.dart';
part 'wishlist_state.dart';
class WishlistBloc extends Bloc<WishlistEvent, WishlistState> {
WishlistBloc() : super(WishlistLoading()) {
on<StartWishlist>(_mapStartWishlistToState);
on<AddWishlistProduct>(_mapAddWishlistToState);
on<RemoveWishlistProduct>(_mapRemoveWishlistToState);
}
void _mapStartWishlistToState(
// Added argument types below
StartWishlist event, Emitter<WishlistState> emit) async {
emit(WishlistLoading());
try {
await Future.delayed(Duration(seconds: 1));
emit(WishlistLoaded());
} catch (_) {}
}
void _mapAddWishlistToState(
// Added argument types below
AddWishlistProduct event, Emitter<WishlistState> emit) async {
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from(state.wishlist.products)
..add(event.product))));
} catch (_) {}
}
}
void _mapRemoveWishlistToState(
// Added argument types below
RemoveWishlistProduct event, Emitter<WishlistState> emit) async {}
}
Type promotion is impossible for properties, since they can potentially return different values each time they are called. As such, it is impossible for the compiler to know that the state getter will return a WishlistLoaded instance, even after knowing that the same getter returned a WishlistLoaded four lines earlier.
One way around this is to assign the state to a local variable, which is eligible for type promotion.
void _mapAddWishlistToState(AddWishlistProduct event, Emitter<WishlistState> emit) async {
final state = this.state; // local variable
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from(state.wishlist.products)
..add(event.product))));
} catch (_) {}
}
}
The linked video used a parameter, which is also eligible for type promotion. If it had used the state getter directly in _mapAddWishlistProductToState, it would have run into the same error.
You just need to cast your state as following
void _mapAddWishlistToState(event, emit) async {
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from((state as WishlistLoaded).wishlist.products)
..add(event.product))));
} catch (_) {}
}
}

extending extended class or how to get code out of BLoC

since one cannot extend an extension of a class such as class MyBloc extends Bloc<MyEvent, MyState>, what is a cleanest way to get some functions out of my bloc file? Since the logic is a bit more complex, I would like to get some sub functions out like in
#override
Stream<MyState> mapEventToState(MyEvent event) async* {
yield* event.map(
loadRequested: (e) => _mapLoadRequestedToState(),
dataEntered: (e) => _mapDataEnteredToState(),
);
}
Stream<LogicChainState> _mapLoadRequestedToState() async* {
final dataRaw = loadData();
final dataProc = initData(dataRaw);
yield doSomeMore(dataProc);
}
I don't like the idea of using global functions. I could create a class
class MyBlocUtils {
MyData initData(MyData dataRaw) {
...
}
MyData doSomeMore(MyData dataProc) {
...
}
}
which still isn't as nice as using a function defined within the class MyBloc.
Any advice?
If all you want is to separate your methods into multiple files, but keep them in the same class, you could use extension methods.
my_bloc.dart
part 'my_bloc_utils.dart';
class MyBloc extends Bloc<MyEvent, MyState> {
Stream<LogicChainState> _mapLoadRequestedToState() async* {
final dataRaw = loadData();
final dataProc = initData(dataRaw);
yield doSomeMore(dataProc);
}
}
my_bloc_utils.dart
part of 'my_bloc.dart';
extension MyBlocUtils on MyBloc {
#override
Stream<MyState> mapEventToState(MyEvent event) async* {
yield* event.map(
loadRequested: (e) => _mapLoadRequestedToState(),
dataEntered: (e) => _mapDataEnteredToState(),
);
}
}
You can access the methods in just the same way as you keep everything in a single file:
import 'my_bloc.dart';
final myBloc = MyBloc();
final stream = myBloc.mapEventToState(MyEvent());

Flutter/Riverpod: Call method within StateNotifier from another StateNotifier

How can I call the method of one StateNotifier from another StateNotifier? I want to call addNewHabit (in the top class) from submitData (in the lower class).
Here are the bodies of the classes:
class HabitStateNotifier extends StateNotifier<List<Habit>> {
HabitStateNotifier(state) : super(state ?? []);
void startAddNewHabit(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (_) {
return NewHabit();
});
}
//this one right here
void addNewHabit(String title) {
final newHabit = Habit(title: title);
state.add(newHabit);
}
void deleteHabit(String id) {
state.removeWhere((habit) => habit.id == id);
}
}
and
class TitleControllerStateNotifier
extends StateNotifier<TextEditingController> {
TitleControllerStateNotifier(state) : super(state);
void submitData() {
if (state.text.isEmpty) {
return;
} else {
//call 'addNewHabit' from above class
}
}
}
What is the correct way to do this?
Technically, not a recommended pattern to use as StateNotifiers are controllers and you shouldn't really be calling controllers inside controllers.
But this is pretty easy to accomplish as you can read other providers inside a provider.
final habitProvider = StateNotifierProvider((ref) => HabitStateNotifier());
final userControllerProvider = StateNotifierProvider((ref) {
return UserController(
habitProvider : ref.read(habitProvider),
);
});
And then use the reference and call
class TitleControllerStateNotifier
extends StateNotifier<TextEditingController> {
TitleControllerStateNotifier(state, HabitStateNotifier habitProvider) :
habit = habitProvider,
super(state);
final HabitStateNotifier habit;
void submitData() {
if (state.text.isEmpty) {
return;
} else {
habit.addNewHabit(state.text);
}
}
}
Set up a StateNotifierProvider (from Riverpod), which will give you back a StateNotifier after running the create callback. This callback has a (ref) parameter on which you can call ref.read and ref.watch to fetch other providers in either non-depending or depending mode.