阅读 69

Flutter 学习之旅(四十四) Flutter 状态 BLoC学习

什么是BLoC

BLoC(Business Logic Component)是谷歌提出的一种设计模式,利用Flutter 响应式结构的特点,通过Stream 的方式实现了异步渲染界面,实现布局与业务分离的效果.

这句话中有2个重点,一个是异步渲染,另一个就是业务与布局分离,
以传统的计数器代码为例,我们想要实现这个功能其实是非常简单的

class TsmSimpleBLoCPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _TsmSimpleBLoCState();
}

class _TsmSimpleBLoCState extends State<TsmSimpleBLoCPage> {
  int count = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Simple BLoC 学习'),
        centerTitle: true,
      ),
      body: Container(
        alignment: Alignment.center,
        child: Text(count.toString()),
      ),
      floatingActionButton: FloatingActionButton(
        child: Text('+'),
        onPressed: () {
          setState(() {
            ++count;
          });
        },
      ),
    );
  }
}

使用了BLoC后的代码结构如下

class TsmSimpleBLoCPage extends StatefulWidget{
  @override
  State<StatefulWidget> createState()=>_SimpleBLoCState();

}

class _SimpleBLoCState extends State<TsmSimpleBLoCPage>{

  _SimpleBLoC _bloc;
  @override
  void initState() {
    super.initState();
    _bloc=_SimpleBLoC.of();
  }
  @override
  void dispose() {
    _bloc.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Simple BLoC 学习'),
        centerTitle: true,
      ),
      body: StreamBuilder(
        stream:_bloc.outStream ,
        initialData: _bloc.data,
        builder: (context,snap){
          return Container(
            alignment: Alignment.center,
            child: Text(snap.data.toString(),style: TextStyle(fontSize: 18),),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        child: Text('+'),
        onPressed: (){
          _bloc.add();
        },
      ),
    );
  }
}

class _SimpleBLoC{
  int _data;
   StreamController _streamController;
  _SimpleBLoC.of(){
     _streamController=StreamController<int>();
     _data=0;
  }

  dispose(){
    _streamController.close();
  }
  get outStream=>_streamController.stream;

  get data=>_data;

  add(){
    _streamController.sink.add(++_data);
  }
}

使用了BLoC 后,代码变得更多了,那么为什么还要使用他呢,如果你是一个android 开发的话,看起来是不是和MVP结构有点类似, 将逻辑代码与UI分离开,在state 的 build Widget 的方法内没有一行是关于业务逻辑的,在bloc 模块没有一行是关于布局的,这点主要是为了避免Widget 中嵌套了太多关于业务逻辑的判断,导致随着业务发展难以维护,虽然看到有网上说这么做是减少build 的次数,但是看了StreamBuilder 的源码发现,他用来更新widget的方法就是setState 的方法,并没有减少build的次数,只是通过观察者模式减少了业务与数据之间的关系
在使用bloc的过程中尽量使用StatefulWidget,这样你可以使用 dispose 方法来关闭stream ,减少内存泄露

我们先来看一下StreamBuilder 的源码,再来自己封装一个BLoC形式的StatefulWidget,以便于加深印象

class StreamBuilder<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> {
  const StreamBuilder({
    Key key,
    this.initialData,
    Stream<T> stream,
    @required this.builder,
  }) : assert(builder != null),
       super(key: key, stream: stream);
  final T initialData;
  @override
  AsyncSnapshot<T> initial() => AsyncSnapshot<T>.withData(ConnectionState.none, initialData);
  @override
  AsyncSnapshot<T> afterConnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.waiting);
  @override
  AsyncSnapshot<T> afterData(AsyncSnapshot<T> current, T data) {
    return AsyncSnapshot<T>.withData(ConnectionState.active, data);
  }
  @override
  AsyncSnapshot<T> afterError(AsyncSnapshot<T> current, Object error) {
    return AsyncSnapshot<T>.withError(ConnectionState.active, error);
  }
  @override
  AsyncSnapshot<T> afterDone(AsyncSnapshot<T> current) => current.inState(ConnectionState.done);
  @override
  AsyncSnapshot<T> afterDisconnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.none);
  @override
  Widget build(BuildContext context, AsyncSnapshot<T> currentSummary) => builder(context, currentSummary);
}

入参接收了一个initialData,在初始化AsyncSnapshot 的时候将这个initialData 添加进去,其他方便并没有什么逻辑,继续看他的父类

abstract class StreamBuilderBase<T, S> extends StatefulWidget {
  const StreamBuilderBase({ Key key, this.stream }) : super(key: key);
  final Stream<T> stream;
  S initial();
  S afterConnected(S current) => current;
  S afterData(S current, T data);
  S afterError(S current, Object error) => current;
  S afterDone(S current) => current;
  S afterDisconnected(S current) => current;

  Widget build(BuildContext context, S currentSummary);

  @override
  State<StreamBuilderBase<T, S>> createState() => _StreamBuilderBaseState<T, S>();
}

class _StreamBuilderBaseState<T, S> extends State<StreamBuilderBase<T, S>> {
  StreamSubscription<T> _subscription;
  S _summary;

  @override
  void initState() {
    super.initState();
    _summary = widget.initial();
    _subscribe();
  }
  
  @override
  void didUpdateWidget(StreamBuilderBase<T, S> oldWidget) {
    super.didUpdateWidget(oldWidget);
    ///前后的数据源不同,先取消注册,再重新注册,
    if (oldWidget.stream != widget.stream) {
      if (_subscription != null) {
        _unsubscribe();
        _summary = widget.afterDisconnected(_summary);
      }
      _subscribe();
    }
  }

  @override
  Widget build(BuildContext context) => widget.build(context, _summary);
  
///在控件移出的时候取消注册
  @override
  void dispose() {
    _unsubscribe();
    super.dispose();
  }
  ///添加各种监听
  void _subscribe() {
    if (widget.stream != null) {
      _subscription = widget.stream.listen((T data) {
        setState(() {
          _summary = widget.afterData(_summary, data);
        });
      }, onError: (Object error) {
        setState(() {
          _summary = widget.afterError(_summary, error);
        });
      }, onDone: () {
        setState(() {
          _summary = widget.afterDone(_summary);
        });
      });
      _summary = widget.afterConnected(_summary);
    }
  }
  
//取消注册
  void _unsubscribe() {
    if (_subscription != null) {
      _subscription.cancel();
      _subscription = null;
    }
  }
}

和包裹inheritedwidget 的statefulwidget 的使用方式基本是保持一致的,在initState 和dispose 方法中分别添加和移除监听,在didUpdateWidget方法中如果有必要则重置监听,在build 的方法内向下暴露当前的data

接下来我们利用StatefulWidget 与 StreamBuilder 封装一个简单的bloc 的base类,方便使用,

abstract class TsmBaseBLoC{

  /**
   *  用来调用 streamcontroller.close()
   */
  void dispose();
}


class TsmBaseBLoCWidget<T extends TsmBaseBLoC > extends StatefulWidget{
  final Widget child;
  final T bloc;
  TsmBaseBLoCWidget({Key key, @required this.child, @required this.bloc}):super(key:key);
  @override
  State<StatefulWidget> createState() =>_TsmBaseBLoCState<T>();
  /**
   * 便于子Widget 通过此方法向上
   * 此方法不能在 TsmBaseBLoCWidget 的child直接使用,需要使用StatelessWidget 或者StatefulWidget
   * 包裹一层,原因是 在findAncestorWidgetOfExactType 这个方法是直接查找他们_parent ,并没有比对自身
   * 他的直接 包裹的子Widget 的bloc 直接使用初始化的就可以了,
   *
   */
  static T of<T extends TsmBaseBLoC>(BuildContext context){
    TsmBaseBLoCWidget<T> provider = context.findAncestorWidgetOfExactType<TsmBaseBLoCWidget<T>>();
    return provider.bloc;
  }

}

class _TsmBaseBLoCState<T> extends State<TsmBaseBLoCWidget>{
  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
  @override
  void dispose() {
    widget.bloc.dispose();
    super.dispose();
  }
}

封装后自动实现自动streamcontroller.close 和 在TsmBaseBLoCWidget 的子 StatelessWidget 与StatefulWidget中可以使用var_bloc=TsmBaseBLoCWidget.of<T>(context);来获取该 bloc,

关于findAncestorWidgetOfExactType 方法引起的血案

这个方法的意思向上查找祖先widget,找到第一个符合条件的widget为止,既然是这样,正常情况下A页面启动B页面,是不是会通过 A页面加载B页面呢,这样B页面也就继承了A页面的某些特性,答案肯定不是这样的,先来分析一下如果我们这么做会出现什么问题
1> widget 树结构太复杂,打开的widget 的深度越深 则这个widget中包含所有widget的特性,数据量太大,这个太容易导致oom了,
2> 如果从InheritedWidget 方面来说,如果A启动B,B就会继承A的特性,设计这个InheritedWidget 就完全没有必须要了,

再来从代码上面来讲,打开一个页面使用的Navigator,我看了一下Navigator.of的方法,看一下他是如何实现的,

  static NavigatorState of(
    BuildContext context, {
    bool rootNavigator = false,
    bool nullOk = false,
  }) {
    // Handles the case where the input context is a navigator element.
    NavigatorState navigator;
    if (context is StatefulElement && context.state is NavigatorState) {
        navigator = context.state as NavigatorState;
    }
    if (rootNavigator) {///找到最顶层NavigatorState  ,即MaterialApp 的 navigatorKey
      navigator = context.findRootAncestorStateOfType<NavigatorState>() ?? navigator;
    } else {
    ///找到最近NavigatorState
      navigator = navigator ?? context.findAncestorStateOfType<NavigatorState>();
    }
    return navigator;
  }

在Navitagor.of的方法我们发现他需要一个rootNavigator ,来控制他是获取整个App根NavigatorState 还是向上查找最近一个NavigatorState,
那也就可以理解在整个app中是可以存在多个 NavigatorState, 这样就实现了根NavigatorState 控制了整个app的风格, 最近的NavigatorState 作为模块的根NavigatorState 控制着必要的数据,实现在一个模块共享一些必须的信息,为什么要这么设计,我先来说一下我的需求,以一个机票改期为例, 改期的第一步需要获取到需要改期的行程,根据这个原始行程你需要经过选择日历的界面,选择新航班的页面,选择新仓位的页面,新的确认订单页面,如果你使用的是app 的根NavigatorState,所有的页面是平级的,他们之间不能使用InheritedWidget共享数据 ,将这个数据放在根NavigatorState共享给其他模块又没有什么意义,你如果在选择需要改期的行程页面创建这个NavigatorState , 这个NavigatorState 承接了根NavigatorState 的theme数据,同时又可以利用InheritedWidget 在这个NavigatorState 下面共享这个原始行程的数据,这样实现起来就非常完美了,

这些都是一些理论知识,由于Navigator和NavigatorState 他们是一个控件的整体,就像StatefulWidget 和State 一样,利用Navigator来创建页面我还没有看到,只是一些猜想,而这个灵感就是从findAncestorWidgetOfExactType 这样一个方法而来的,虽然因此思前想后的3个多小时,但是知识的进步真是一件令人愉快的事情
这个里面关于如何实现一个简单的Navigator,希望大家和我多多交流,在后续我也会多加关注这方面的源码,

我学习flutter的整个过程都记录在里面了
https://www.jianshu.com/c/36554cb4c804

最后附上demo 地址

https://github.com/tsm19911014/tsm_flutter

作者:Tsm_2020

原文链接:https://www.jianshu.com/p/3adb64923703

文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐