前言
在 Flutter 的状态管理插件中,BLoC(Business Logic Component)非常受欢迎,事实上在 GitHub 上,BLoC 在众多的状态管理插件中的 Star 是最多的( 共7.8k,Provider 是3.9k,GetX 是4.6k)。这主要的原因是 BLoC 更多的是一种设计模式,按照这种设计模式可以转变为很多种状态管理实现。实际上在 pub 搜索 BLoC 会出现很多相关的插件,当然,官方的还是 bloc 和 flutter_bloc 组合。我们本篇先来介绍一下 BLoC 的基本概念。
BLoC 与 Stream
BLoC 依赖 Stream
和 StreamController
实现,组件通过Sinks
发送更新状态的事件,然后再通过 Streams
通知其他组件更新。事件处理和通知刷新的业务逻辑都是由 BLoC 完成,从而实现业务逻辑与 UI 层的分离(有点类似 Redux),并且逻辑部分可以复用和可以单独进行单元测试。
Flutter 中的 BLoC
bloc package是为了快速在 Flutter 或 Dart 中实现 BLoC 模式的插件,BLoC 官网提供了很多示例和详细的文档,有兴趣深入了解的可以上官网啃英文文档和浏览示例项目的代码。在 BLoC 有三个重要的概念,分别是 Cubit
、 BlocObserver
和BLoC
Cubit
Cubit是管理状态数据的 BlocBase 子类,它可以管理任意类型的数据,包括基本类型到复杂对象。在Cubit
调用 emit
构建新的状态数据前需要给状态数据一个初始值。当状态数据发生改变的时候,会触发 Cubit
的 onChange
回调,如果出现错误则会触发 onError
回调。
class CounterCubit extends Cubit {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
@override
void onChange(Change change) {
super.onChange(change);
print(change);
}
@override
void onError(Object error, StackTrace stackTrace) {
print('$error, $stackTrace');
super.onError(error, stackTrace);
}
}
UI界面可以通过调用 Cubit
对外暴露的更新状态方法触发状态更新,而在 onChange
中会得到更新前后的状态,从而可以触发界面刷新。
[图片上传失败...(image-613730-1648814946206)]
BlocObserver
BlocObserver 可以监听所有的 Cubit 的变化,从而使得我们可以同时监听多个 Cubit。例如运行下面的代码
class CounterCubit extends Cubit {
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();
}
控制台打印结果为:
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
Bloc
Bloc
也是继承 BlocBase
的类,但相比 Cubit
来说更为高级一些。它使用的是 events
而不是暴露的函数来更新状态。
[图片上传失败...(image-6264b6-1648814946206)]
在 Bloc
内部,有一个onEvent
方法,在这个方法里,通过 EventTransformer
将 event
转换为更新状态的方法来刷新状态数据。每个event
都可以有对应的 EventHandler
来处理该 event
,完成后再通过 emit
触发通知状态更新。当状态转变前会调用 onTransition
,在这里会有当前的状态,触发更新的 event
和下一个状态。
[图片上传失败...(image-f9e883-1648814946206)]
用 Bloc 改写之前的 CounterCubit
形式如下,由于 Bloc
也是 BlocBase
的子类,因此也可以使用 Observer
监测它的变化。
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
class CounterBloc extends Bloc {
CounterBloc(int initialState) : super(initialState) {
on((event, emit) => emit(state + 1));
on((event, emit) => emit(state - 1));
}
@override
void onTransition(Transition 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();
}
执行结果如下:
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
总结
从 Bloc
的设计来看,使用了函数(Cubit
形式)和事件( Bloc
形式)的方式来驱动状态数据更新,然后再通过emit
通知状态更新。通过这种方式解耦 UI 界面和业务逻辑。可以看到,Bloc
本身的业务逻辑和界面完全无关,这使得我们可以直接编写测试代码,而无需依赖界面,如同本篇的 main
方法中的代码其实就可以作为单元测试代码来验证业务逻辑是否正确。这使得 Bloc
构建的应用程序的可维护性会更好。