Flutter 入门与实战(八十五):GitHub 高赞的BLoC 简介

·  阅读 1075
Flutter 入门与实战(八十五):GitHub 高赞的BLoC 简介

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

在 Flutter 的状态管理插件中,BLoC(Business Logic Component)非常受欢迎,事实上在 GitHub 上,BLoC 在众多的状态管理插件中的 Star 是最多的( 共7.8k,Provider 是3.9k,GetX 是4.6k)。这主要的原因是 BLoC 更多的是一种设计模式,按照这种设计模式可以转变为很多种状态管理实现。实际上在 pub 搜索 BLoC 会出现很多相关的插件,当然,官方的还是 bloc 和 flutter_bloc 组合。我们本篇先来介绍一下 BLoC 的基本概念。

image.png

BLoC 与 Stream

BLoC 依赖 StreamStreamController实现,组件通过Sinks发送更新状态的事件,然后再通过 Streams 通知其他组件更新。事件处理和通知刷新的业务逻辑都是由 BLoC 完成,从而实现业务逻辑与 UI 层的分离(有点类似 Redux),并且逻辑部分可以复用和可以单独进行单元测试。

image.png

Flutter 中的 BLoC

bloc package是为了快速在 Flutter 或 Dart 中实现 BLoC 模式的插件,BLoC 官网提供了很多示例和详细的文档,有兴趣深入了解的可以上官网啃英文文档和浏览示例项目的代码。在 BLoC 有三个重要的概念,分别是 CubitBlocObserverBLoC

Cubit

Cubit是管理状态数据的 BlocBase 子类,它可以管理任意类型的数据,包括基本类型到复杂对象。在Cubit调用 emit 构建新的状态数据前需要给状态数据一个初始值。当状态数据发生改变的时候,会触发 CubitonChange 回调,如果出现错误则会触发 onError 回调。

image.png

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);
  }
}
复制代码

UI界面可以通过调用 Cubit 对外暴露的更新状态方法触发状态更新,而在 onChange 中会得到更新前后的状态,从而可以触发界面刷新。 image.png

BlocObserver

BlocObserver 可以监听所有的 Cubit 的变化,从而使得我们可以同时监听多个 Cubit。例如运行下面的代码

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();
}
复制代码

控制台打印结果为:

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.png

Bloc 内部,有一个onEvent 方法,在这个方法里,通过 EventTransformerevent转换为更新状态的方法来刷新状态数据。每个event都可以有对应的 EventHandler来处理该 event,完成后再通过 emit 触发通知状态更新。当状态转变前会调用 onTransition,在这里会有当前的状态,触发更新的 event 和下一个状态。

image.png

用 Bloc 改写之前的 CounterCubit 形式如下,由于 Bloc 也是 BlocBase 的子类,因此也可以使用 Observer 监测它的变化。

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();
}
复制代码

执行结果如下:

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 构建的应用程序的可维护性会更好。

我是岛上码农,微信公众号同名,这是Flutter 入门与实战的专栏文章,提供体系化的 Flutter 学习文章。对应源码请看这里:Flutter 入门与实战专栏源码。如有问题可以加本人微信交流,微信号:island-coder

👍🏻:觉得有收获请点个赞鼓励一下!

🌟:收藏文章,方便回看哦!

💬:评论交流,互相进步!

分类:
Android
标签: