Example to show how we can implement the BLOC or Business Logic Component in our Flutter application.
The goal of this package is to make it easy to implement the BLoC Design Pattern (Business Logic Component).
This design pattern helps to separate presentation from business logic. Following the BLoC pattern facilitates testability and reusability. This package abstracts reactive aspects of the pattern allowing developers to focus on writing the business logic.
A Bloc is a more advanced class which relies on events to trigger state changes rather than functions. Bloc also extends BlocBase which means it has a similar public API as Cubit. However, rather than calling a function on a Bloc and directly emitting a new state, Blocs receive events and convert the incoming events into outgoing states.
State changes in bloc begin when events are added which triggers onEvent. The events are then funnelled through an EventTransformer. By default, each event is processed concurrently but a custom EventTransformer can be provided to manipulate the incoming event stream. All registered EventHandlers for that event type are then invoked with the incoming event. Each EventHandler is responsible for emitting zero or more states in response to the event. Lastly, onTransition is called just before the state is updated and contains the current state, event, and next state.
BLOC:
class HeroBloc extends Bloc<HeroEvent, HeroState> {
final HeroNetwork _heroNetwork;
HeroBloc(this._heroNetwork) : super(HeroLoadingState()) {
add(LoadHeroEvent());
}
Stream<HeroState> mapEventToState(HeroEvent event) async* {
if(event is LoadHeroEvent) {
yield HeroLoadingState();
try {
final heros = await _heroNetwork.getHeroData();
yield HeroLoadedState(heros);
} catch (e) {
yield HeroErrorState();
}
}
}
}
EVENT:
abstract class HeroEvent {}
class LoadHeroEvent extends HeroEvent {}
STATE:
@immutable
abstract class HeroState extends Equatable {}
class HeroLoadingState extends HeroState {
@override
List<Object?> get props => [];
}
class HeroLoadedState extends HeroState {
final List<HeroModel> heros;
HeroLoadedState(this.heros);
@override
List<Object?> get props => [heros];
}
class HeroErrorState extends HeroState {
@override
List<Object?> get props => [];
}
class HeroDemoState extends HeroState {
final List<HeroModel> heros;
HeroDemoState(this.heros);
@override
List<Object?> get props => [heros];
BLOC BUILDER:
body: BlocBuilder<HeroBloc, HeroState>(
builder: (context, state) {
if (state is HeroLoadingState) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (state is HeroLoadedState) {
List<HeroModel> heroList = state.heros;
heroList.forEach((element) {
if (element.localisedName == 'Axe') {
debugPrint('AXE FOUND');
}
});
// context.read<HeroCubit>().fetchOneName();
return ListView.builder(
itemCount: heroList.length,
itemBuilder: (_, index) {
return Card(
color: Colors.black,
child: ListTile(
title: Text(
'${heroList[index].localisedName}',
style:
const TextStyle(color: Colors.white, fontSize: 17.0),
),
subtitle: Text(
'${heroList[index].name}',
style: const TextStyle(color: Colors.red),
),
),
);
});
}
if (state is HeroErrorState) {
return Center(
child: Container(
child: const Text('Error')
),
);
}
if (state is HeroDemoState) {
List<HeroModel> demoList = state.heros;
return Center(
child: Container(
child: Text('${demoList[0].name}', style: TextStyle(color: Colors.white),),
),
);
}
return Container();
},
),
A Cubit is class which extends BlocBase and can be extended to manage any type of state. Cubit requires an initial state which will be the state before emit has been called. The current state of a cubit can be accessed via the state getter and the state of the cubit can be updated by calling emit with a new state.
State changes in cubit begin with predefined function calls which can use the emit method to output new states. onChange is called right before a state change occurs and contains the current and next state.
First comes Cubit : It contains the initial state as 0 and two methods for increment and decrement, here int is acting as a state.
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
/// Add 1 to the current state.
void increment() => emit(state + 1);
/// Subtract 1 from the current state.
void decrement() => emit(state - 1);
}
Bloc Provider : Here we are mentioning the initial state, which is 0 in Cubit.
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterCubit(),
child: const CounterView(),
);
}
Bloc Builder :
child: BlocBuilder<CounterCubit, int>(
builder: (context, state) {
return Text('$state', style: textTheme.displayMedium);
},
),
We can call cubit function by:
context.read<CounterCubit>().increment()
///OR
context.read<CounterCubit>().decrement()
Cubit: We are emiting List of Post as state, initial state is empty list and getPosts function helps us to get data from the network request and emit as a list of Post.
class PostCubit extends Cubit<List<Post>> {
final _dataService = DataService();
PostCubit(): super([]);
void getPosts() async => emit(await _dataService.getPost());
}
Bloc Provide : If we want to call any function in cubit when initial state is called by adding three dots after class name and calling the function.
home: BlocProvider<PostCubit>(
create: (context) => PostCubit()..getPosts(),
child: PostView()
),
Bloc Builder :
body: BlocBuilder<PostCubit, List<Post>>(
builder: (context, posts) {
if (posts.isEmpty) {
return const Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(posts[index].title ?? ""),
),
);
});
},
),
In this example we have a state in a separate file.
Cubit:
In this we have a HeroState as a state. We can call a function at a initial state by adding the function like this:
class HeroCubit extends Cubit<HeroState> {
final HeroNetwork _heroNetwork;
HeroCubit(this._heroNetwork) : super(HeroLoadingState()) {
fetchHeroList();
}
void fetchHeroList() async {
emit(HeroLoadingState());
try {
final heros = await _heroNetwork.getHeroData();
emit(HeroLoadedState(heros));
} catch (e) {
emit(HeroErrorState());
}
}
void fetchOneName() {
emit(HeroDemoState([HeroModel(name: 'Big', localisedName: 'Bee')]));
}
}
State:
@immutable
abstract class HeroState extends Equatable {}
class HeroLoadingState extends HeroState {
@override
List<Object?> get props => [];
}
class HeroLoadedState extends HeroState {
final List<HeroModel> heros;
HeroLoadedState(this.heros);
@override
List<Object?> get props => [heros];
}
class HeroErrorState extends HeroState {
@override
List<Object?> get props => [];
}
class HeroDemoState extends HeroState {
final List<HeroModel> heros;
HeroDemoState(this.heros);
@override
List<Object?> get props => [heros];
}
Bloc Provider: We can mention the cubit name at starting of BlocProvider, which helps us to indetify for which cubit it works.
home: BlocProvider<HeroCubit>(
create: (BuildContext context) => HeroCubit(HeroNetwork()),
child: HeroView()
),
Bloc Builder: Now we can show the UI for different state.
body: BlocBuilder<HeroCubit, HeroState>(
builder: (context, state) {
if (state is HeroLoadingState) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (state is HeroLoadedState) {
List<HeroModel> heroList = state.heros;
heroList.forEach((element) {
if (element.localisedName == 'Axe') {
debugPrint('AXE FOUND');
}
});
// context.read<HeroCubit>().fetchOneName();
return ListView.builder(
itemCount: heroList.length,
itemBuilder: (_, index) {
return Card(
color: Colors.black,
child: ListTile(
title: Text(
'${heroList[index].localisedName}',
style:
const TextStyle(color: Colors.white, fontSize: 17.0),
),
subtitle: Text(
'${heroList[index].name}',
style: const TextStyle(color: Colors.red),
),
),
);
});
}
if (state is HeroErrorState) {
return Center(
child: Container(
child: const Text('Error')
),
);
}
if (state is HeroDemoState) {
List<HeroModel> demoList = state.heros;
return Center(
child: Container(
child: Text('${demoList[0].name}', style: TextStyle(color: Colors.white),),
),
);
}
return Container();
},
),