Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
preface
BLoC (Business Logic Component) is very popular among Flutter state management plug-ins. In fact, BLoC has the highest Star among all the Flutter state management plug-ins on GitHub (total 7.8K, Provider 3.9K). GetX is 4.6K). The main reason for this is that BLoC is more of a design mode, according to which a variety of state management can be implemented. In fact, searching for BLoC in the pub will bring up a number of related plug-ins, of course, the official combination of BLoC and Flutter_bloc. Let’s look at the basic concept of BLoC.
BLoC and Stream
BLoC relies on Stream and StreamController implementation. The component sends status update events through Sinks and then notifies other components of updates through Streams. The business logic for both event processing and notification refresh is done by BLoC, allowing for a separation of business logic from the UI layer (somewhat like Redux), and the logic part can be reused and unit-tested separately.
Flutter in the BLoC
Bloc Package is a plug-in designed to quickly implement the Bloc mode in Flutter or Dart. The Bloc website provides many examples and detailed documentation. If you are interested in more information, you can browse the English documents and sample project codes on the website. There are three important concepts in BLoC, namely Cubit, BlocObserver and BLoC
Cubit
Cubit is a BlocBase subclass that manages state data. It can manage any type of data, from basic types to complex objects. An initial value is required before Cubit calls emit to build the new state data. Cubit’s onChange callback is triggered when the state data changes, and the onError callback is triggered if there is an error.
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
@override
void onChange(Change<int> change) {
super.onChange(change);
print(change);
}
@override
void onError(Object error, StackTrace stackTrace) {
print('$error.$stackTrace');
super.onError(error, stackTrace); }}Copy the code
The UI interface can be calledCubit
The externally exposed update status method triggers a status update while theonChange
To get the status before and after the update, which can trigger the interface refresh.
BlocObserver
BlocObserver listens for all cubits, allowing us to listen for multiple Cubits at the same time. For example, run the following code
class CounterCubit extends Cubit<int> {
CounterCubit({initial = 0}) : super(initial);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
class MyBlocObserver extends BlocObserver {
@override
void onCreate(BlocBase bloc) {
print('BloC Observer onCreate: ${bloc.state}');
super.onCreate(bloc);
}
@override
void onChange(BlocBase bloc, Change change) {
print('BloC Observer onChange: $change');
super.onChange(bloc, change);
}
@override
void onClose(BlocBase bloc) {
print('BloC Observer onClose: ${bloc.state}');
super.onClose(bloc);
}
@override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
print('Bloc Observer onError: $error.$stackTrace');
super.onError(bloc, error, stackTrace);
}
@override
void onEvent(Bloc bloc, Object? event) {
print('Bloc Observer onEvent: $event.${bloc.state}');
super.onEvent(bloc, event); }}void main() {
Bloc.observer = MyBlocObserver();
final cubit = CounterCubit();
cubit.increment();
print('after increment: ${cubit.state}');
cubit.decrement();
print('after decrement: ${cubit.state}');
final anotherCubit = CounterCubit();
anotherCubit.increment();
cubit.close();
anotherCubit.close();
}
Copy the code
The console printed the following result:
BloC Observer onCreate: 0
BloC Observer onChange: Change { currentState: 0, nextState: 1 }
BloC Observer onChange: Change { currentState: 1, nextState: 0 }
BloC Observer onCreate: 10
BloC Observer onChange: Change { currentState: 10, nextState: 11 }
BloC Observer onClose: 0
BloC Observer onClose: 11
Copy the code
Bloc
Bloc is also a descendant of BlocBase, but is a bit more advanced than Cubit. It uses events instead of exposed functions to update the state.
Within Bloc, there is an onEvent method in which the state data is refreshed by an EventTransformer that converts events into updated states. Each event can have a corresponding EventHandler to handle the event and emit status updates after completion. OnTransition is called before a state transition, where you have the current state, the event that triggers the update, and the next state.
Bloc overwrites the previous CounterCubit form as follows, and since Bloc is also a subclass of BlocBase, you can also use an Observer to monitor its changes.
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent.int> {
CounterBloc(int initialState) : super(initialState) {
on<IncrementEvent>((event, emit) => emit(state + 1));
on<DecrementEvent>((event, emit) => emit(state - 1));
}
@override
void onTransition(Transition<CounterEvent, int> transition) {
print(
'Current: ${transition.currentState}, Next: ${transition.nextState}, Event: ${transition.event}');
super.onTransition(transition); }}void main() {
Bloc.observer = MyBlocObserver();
final counterBloc = CounterBloc(5);
counterBloc.add(IncrementEvent());
counterBloc.add(DecrementEvent());
counterBloc.close();
}
Copy the code
The result is as follows:
Current: 5, Next: 6, Event: Instance of 'IncrementEvent'
BloC Observer onChange: Change { currentState: 5, nextState: 6 }
Current: 6, Next: 5, Event: Instance of 'DecrementEvent'
BloC Observer onChange: Change { currentState: 6, nextState: 5 }
BloC Observer onClose: 5
Copy the code
conclusion
From the design of Bloc, functions (Cubit form) and events (Bloc form) are used to drive status data updates and then emit notifications of status updates. Decouple the UI interface from the business logic in this way. As you can see, the business logic of Bloc itself is completely independent of the interface, which allows us to write test code directly without relying on the interface, as the code in the main method in this paper can actually be used as unit test code to verify the correctness of the business logic. This makes the application built by Bloc much more maintainable.
I am dao Code Farmer with the same name as my wechat official account. This is a column about the introduction and practice of Flutter, providing systematic learning articles about Flutter. See the corresponding source code here: The source code of Flutter Introduction and Practical column. If you have any questions, please add me to the wechat account: island-coder.
👍🏻 : feel the harvest please point a praise to encourage!
🌟 : Collect articles, easy to look back!
💬 : Comment exchange, mutual progress!