Flutter

FlutterBloc实战应用

2020-09-30  本文已影响0人  嘛尼嘛哄

Flutter Bloc

英文全称(business logic),用于处理业务逻辑,其内部实现主要是对Stream的输入和输出进行了封装,它的实现原理是利用RxDart(基于Stream封装)提供的PublishSubjectBehivorSubject实现了EventState之间的转换,以及利用Flutter提供的局部状态管理控件InheritedWidget来传递Bloc对象,涉及的类比较多,但基本都围绕着对上面这个对象的封装。

Usage

https://bloclibrary.dev/

WorkFlow

截屏2020-09-30 上午6.26.53.png

主要类之间的关系

FlutterBloc Classes.png

实现步骤

  1. 首先自定义Bloc

    自定义一个继承Bloc的类,并实现mapEventToState的方法逻辑,完成事件输入到数据输出之间的转换,

  2. 注册Bloc

BlocProvider(
    create: (context) => MyCustomBloc(),
    child: YourCustomWidget(),
);

-BlocProvider在build时他会在内部创建一个InheritedProvider,它接受了一个Bloc作为值,由于InheritedProvider是继承于InheritedWidget,所以Bloc就可以通过InhertedElement间接的存储到当前的的Element中,它的child内部就能直接通过Context获取到.

class BlocProvider<T extends Bloc<dynamic, dynamic>>
    extends ValueDelegateWidget<T> implements SingleChildCloneableWidget { 
  final Widget child;

  //BlocProvider销毁时bloc会被自动关闭
  BlocProvider({ ...
    @required ValueBuilder<T> create,//在init时执行此方法创建bloc
    Widget child,
  })  

  //BlocProvider销毁时bloc不会自动关闭
  BlocProvider.value({ ...
    @required T value, //bloc
    Widget child, //具体的页面
  });

  //这里只是用`Provider`的类方法包装了一层,将通过InheritedElment获取方法`context.inheritFromWidgetOfExactType(type) as InheritedProvider<T>`
  static T of<T extends Bloc<dynamic, dynamic>>(BuildContext context) { ...
    
  //通过`InheritedProvider`存储bloc,提供provider获取的方法
  @override
  Widget build(BuildContext context) {
    return InheritedProvider<T>(
      value: delegate.value,
      child: child,
    );
  }
  
  //方便MutliBlocProvider使用
  @override
  BlocProvider<T> cloneWithChild(Widget child) {
    return BlocProvider<T>._(
      key: key,
      delegate: delegate,
      child: child,
    );
  }
}
  1. Bloc使用方式一般有三种,
class BlocBuilder .. extends BlocBuilderBase { ...
 
  final BlocWidgetBuilder<S> builder;

  /// {@macro blocbuilder}
  const BlocBuilder({
    Key key,
    @required this.builder,
    B bloc,
    BlocBuilderCondition<S> condition,)

  @override
  Widget build(BuildContext context, S state) => builder(context, state);
}

class _BlocBuilderBaseState<B extends Bloc<dynamic, S>, S>
    extends State<BlocBuilderBase<B, S>> { ... 
  //记录当前bloc的订阅,用于在销毁时释放订阅
  StreamSubscription<S> _subscription;
  //状态值记录,用于回调给 api调用者过滤不同的 condition,决定当前的state是否需要触发新的构建
  S _previousState;
  S _state;
  B _bloc;

  @override
  void initState() {
    super.initState();
    //可以看到bloc还可以直接重BlocBuilder传入,但这样就不支持BlocProvider便利获取了
    _bloc = widget.bloc ?? BlocProvider.of<B>(context);
     ...
    _subscribe();
  }

 //更新bloc的订阅
  @override
  void didUpdateWidget(BlocBuilderBase<B, S> oldWidget) { ...
  
  //外部widget构建的回调方法
  @override
  Widget build(BuildContext context) => widget.build(context, _state);
  
  //释放bloc的订阅
  @override
  void dispose() {
    _unsubscribe();
    super.dispose();
  }
  
  //监听bloc,并跳过intialState,因为在initial方法中已经拿到了
  void _subscribe() {
    if (_bloc != null) {
      _subscription = _bloc.skip(1).listen((S state) {
        if (widget.condition?.call(_previousState, state) ?? true) {
          setState(() { //很熟悉的setState
            _state = state;
            _previousState = state;
            ...
}
class BlocListener<B extends Bloc<dynamic, S>, S> extends BlocListenerBase<B, S>
    with SingleChildCloneableWidget {
 
  //初始化BlocListener,指定需要过滤的条件和设置listener执行的逻辑
  const BlocListener({
    Key key,
    @required BlocWidgetListener<S> listener,
    B bloc,
    BlocListenerCondition<S> condition,
    this.child,
  })  
  //拷贝child到当前的BlocListener中
  @override
  BlocListener<B, S> cloneWithChild(Widget child) { ...
  @override
  Widget build(BuildContext context) => child;
}

//Listener的逻辑实现
class _BlocListenerBaseState<B extends Bloc<dynamic, S>, S>
    extends State<BlocListenerBase<B, S>> {
  StreamSubscription<S> _subscription;
  S _previousState;
  B _bloc;

  @override
  void initState() {
    super.initState();
    //可以自带一个bloc,但不支持其它子widget通过BlocProvider便利获取
    _bloc = widget.bloc ?? BlocProvider.of<B>(context);
    _previousState = _bloc?.state;
    _subscribe();
  }
  
  //更新当前的bloc,重新订阅
  @override
  void didUpdateWidget(BlocListenerBase<B, S> oldWidget) { ...
  //构建child widget,这里只是by pass,没有做任何操作
  @override
  Widget build(BuildContext context) => widget.build(context);

  //订阅bloc state 
  void _subscribe() {
    if (_bloc != null) {
      _subscription = _bloc.skip(1).listen((S state) {
        if (widget.condition?.call(_previousState, state) ?? true) { //condition的过滤条件在这里
          widget.listener(context, state);
          _previousState = state;
        }
      });
    }
  }

  void _unsubscribe() { ...
  1. 为了便面地狱式的嵌套BlocProvider/BlocListener,引入了MultiBlocListenerMultiBlocProvider,他们属于StatelessWidget,主要是利用一个数组存储多个BlocProviderBlocListener,结合SingleChildCloneableWidget协议的实现
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: listeners,
      child: child,
    );
  }

// SingleChildCloneableWidget 协议的实现
    @override
  Widget build(BuildContext context) {
    var tree = child;
    for (final provider in providers.reversed) {
      tree = provider.cloneWithChild(tree);
    }
    return tree;
  }

  @override
  MultiProvider cloneWithChild(Widget child) {
    return MultiProvider(
      key: key,
      providers: providers,
      child: child,
    );
  }

Bloc数据缓存

主要是通过构建中间基类,对Bloc的initialState和translation事件进行传递并缓存数据到storage.可以参考HydratedBloc实现,以Bloc官方提供的HydratedBloc为例,它主要包括HydratedBloc,Your CustomBlocBloc之间插入的基本,用于管理Bloc数据的缓存.HydratedBlocDelegate它继承了BlocDelegate,用于替换Bloc框架内部默认的BlocDelegate,拦截BlocTranslation中的两个State并存储其每次变化的状态到缓存中去.

关键部分的代码实现:

abstract class HydratedBloc<Event, State> extends Bloc<Event, State> {
  //内置一个默认的Storage,
  //BlocSupervisor.delegate这个是改写之后的 BlocDelegate
  final HydratedStorage _storage =
      (BlocSupervisor.delegate as HydratedBlocDelegate).storage;
  
  //初始化时从缓存获取数据,并在子类自定义的Bloc中实现这个三个方法,
  @mustCallSuper
  @override
  State get initialState { ...
  State fromJson(Map<String, dynamic> json);
  Map<String, dynamic> toJson(State state);
  
  //HydratedBloc内部使用,用于提供BlocState的key
  String get id => ''; //可以定义扩展缓存的key名字,用于区分不同的业务逻辑数据,一般公共的bloc逻辑类可以采用此类方法处理
  Future<void> clear() => _storage.delete('${runtimeType.toString()}$id'); 
}

//Bloc state拦截,和保存

class HydratedBlocDelegate extends BlocDelegate { ...
  final HydratedStorage storage; 
  static Future<HydratedBlocDelegate> build({
    Directory storageDirectory,
  }) async {
    return HydratedBlocDelegate(
      await HydratedBlocStorage.getInstance(storageDirectory: storageDirectory),
    );
  }

  /// {@macro hydratedblocdelegate}
  HydratedBlocDelegate(this.storage);
  
  //关键步骤
  @override
  void onTransition(Bloc bloc, Transition transition) { 
        storage.write('${bloc.runtimeType.toString()}${bloc.id}',
          json.encode(stateJson)


//这个类逻辑单一,只负责文件存储,提供一个存储的Storage给上面的`HydratedBlocDelegate`
class HydratedBlocStorage implements HydratedStorage {...
   static Future<HydratedBlocStorage> getInstance({
    Directory storageDirectory,
   })
   Future<void> delete(String key) async {
   Future<void> delete(String key) async {
   Future<void> clear() async {

通过上面State部分的代码可以看出,此类bloc的设计主要是对Bloc的initialState和translation拦击来自动保存数据,默认提供了一个key,如果有多个不同的state则需使用多个state.这种写法其实对性能有一定影响。因为它是基于状态值实施存储的,所以这就需要我们做一些额外的优化。

Bloc获取网络数据

需要提前注册Repository,注册方式同Bloc类似,然后将Repository赋值给bloc,这样在bloc内部的mapEventToState的方法中就可以根据不同的event时间去访问网络请求了。

Bloc中的坑

  1. Bloc中所有的类型都是采用泛型推导的,这就要求我们在使用的时候不要忘记了指定我们它的类型.
BlocProvider<CustomBloc>.of(context);

BlocBuilder<CustomBloc,CustomBlocState>(builder: (context, state) { ...

BlocListener<CustomBloc,CustomBlocState>(listener: (preState, currentState){ ...
  1. BlocPovider作为Bloc载体,Bloc数据的存储会最终通过InheritedWidget存储在它当前所注册的作用阈中,如果我们在不同的作用域下注册Bloc,那么它将会被低级的作用域覆盖,因为此时WidgetTree中插入了2个不同层级的InheritedWidget<CustomBloc>,
    如果想让他们共享统一个bloc,就必须要保证InheritedWidget所关联的Bloc是同一个value.根据前面对Bloc创建提到的2个类可以看出,Bloc创建分2中方法:

*另一种是通过BuilderStateDelegate来创建Bloc,它通过一个构造函数create(BuildContext context) =>BlocProvider<CustomBloc>(context)`,需要注意的是后面的Bloc需要从上游已注册的Bloc中获取。

3.提到坑这里最近发掘了一个由FutureBuilder引起的bug,
在RootWidget下initialState中初始化了很多全局单例的bloc,然后在该widget的builder中使用了FutureBuilder异步构建BlocProvider对象,BlocProvider对象。但是在每次热重载的时候发现了所有的bloc全部被管理,最后排查了Bloc的作用域,自定义实现BlocProvider断点分析,bloc创建和初始化均无异常,排查FutureBuilder,它根据初始化的future生成done和其他的状态的widget,由于是启动入口,一般只会初始化一次,所以一开始没太注意,但最终发现运来是因为2个bugfix间接导致, 第一个是由于ios平台1.17渲染异常,加上了后台静止切换设置setState来避免UI渲染部分缺失的问题,所以在某些情况下这个FutureBuilder并不是真正的第一次创建,理论上来说只要这个future没变应该也不会出现另外一个rootWidget重新创建,然而恰哈就是因为之前捕捉futureBuilder传递的future(它是项目core service初始化的所有异步task的合并)在catchError的鬼使神差下,每次rootWidget构建都会触发futureBuilde的重新生成另外一个wiget重建,这样所有的BlocProvider都是移除后重建,就出现了类似的问题.
总结之后得出2点结论:
FutureBuilder作用域下面

总结

Bloc框架目前已有5.6k左右,这种简单的固定格式的写法非常适合快速构建项目架构,由于它是基于stream来实现的,所以使用中要特别注意订阅的销毁,避免出现内存的泄漏,和冗余的订阅。它的数据传递利用了InheritedWidget的局部状态刷新来完成层,相比使用常规的stateState来刷新获取数据会更搞效率,这一点上也基本上参照了Flutter官方的设计,比如ThemeData,Localization,MediaQuery等全局数据的获取.它局部Widget构建BlocBuilder依然采用的setState方式构建,因此我们需要尽量避免在BlocBuilder过多的业务逻辑处理,和不必要widget渲染,尽量把静态的widget移出去,业务逻辑我们可以采用BlocListenerBlocBuilder包裹,来优化代码结构,和减少BlocBuilder的次数.

上一篇下一篇

猜你喜欢

热点阅读