Flutter 87: 初识状态管理 Bloc (二)

作者: 阿策神奇 | 来源:发表于2020-05-08 18:41 被阅读0次

          小菜前两天刚学习了基本的 Bloc 状态管理,其中 UI 通过 setState() 方式更新数据,今天进一步了解进阶版的 FlutterBloc 状态管理;

    FlutterBloc

          FlutterBloc 可以更便利的实现 Bloc,主要是为了与 Bloc 共同使用而构建的;同样需要提前了解几个概念;小菜继续以上一节中的 Demo 进行扩展,添加了 Number 的递增和递减;

    BlocBuilder

          BlocBuilder 小菜理解为 Bloc 构造器,主要用于构建 Widget 以响应新的状态,相较于 StreamBuilder 更便捷;可替代小菜上一节使用的 setState()

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

          分析源码可知,builder 用于相应状态的 Widgetbloc 为当前提供的范围仅限于单个 Widget 且无法通过父级 BlocProvider 和当前级访问的 Bloc 时才使用;而 condition 为可选的过度细粒度,包括两个参数,之前的状态和当前的状态,返回值为 Boolean 类型,true 为更新状态重建 Widgetfalse 时不重新构建;

    @override
    Widget build(BuildContext context) {
      return BlocBuilder<NumberBloc, int>(
          bloc: _numBloc,
          condition: (previousState, state) {
            print('BlocPage.condition->$previousState==$state');
            return state <= 30 ? true : false;
          },
          builder: (context, count) {
            return Scaffold(
                appBar: AppBar(title: Text('Bloc Page')),
                body: Center(
                    child: Column( mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                      Text('当 Number > 30 时,不进行变更', style: TextStyle(fontSize: 20.0, color: Colors.blue)),
                      SizedBox(height: 20.0),
                      Text('当前 Number = ${_numBloc.state}', style: TextStyle(fontSize: 20.0, color: Colors.blue))
                    ])),
                floatingActionButton: Column(
                    crossAxisAlignment: CrossAxisAlignment.end,
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: <Widget>[
                      FloatingActionButton(
                          heroTag: 'addTag', child: Icon(Icons.add),
                          onPressed: () => _numBloc.add(NumberEvent.addEvent)),
                      SizedBox(height: 20.0),
                      FloatingActionButton(
                          heroTag: 'removeTag', child: Icon(Icons.remove),
                          onPressed: () => _numBloc.add(NumberEvent.removeEvent))
                    ]));
          });
    }
    

    BlocProvider

          BlocProviderBloc 的供应者,创建 Bloc 并供应给其子控件树;

    BlocProvider({
        Key key,
        @required Create<T> create,
        Widget child,
        bool lazy,
    })
    

          简单了解源码可知,BlocProvider 通过 create 创建一个 Bloc;通过 child 设置用来响应状态的变更的 Widgetlazy 为是否懒创建(延迟创建),小菜理解的为是否在使用时再进行创建,默认为 true

    class _BlocPageState extends State<BlocPage> {
    
      @override
      Widget build(BuildContext context) {
        return BlocProvider(
            create: (BuildContext context) => NumberBloc(),
            child: BlocBuilder<NumberBloc, int>(
                condition: (previousState, state) {
              print('BlocPage.condition->$previousState==$state');
              return state <= 30 ? true : false;
            }, builder: (context, count) {
              return Scaffold(
                  appBar: AppBar(title: Text('Bloc Page')),
                  body: Center(
                      child: Column( mainAxisAlignment: MainAxisAlignment.center,
                          children: <Widget>[
                        Text('当 Number > 30 时,不进行变更', style: TextStyle(fontSize: 20.0, color: Colors.blue)),
                        SizedBox(height: 20.0),
                        Text('当前 Number = ${BlocProvider.of<NumberBloc>(context).state}', style: TextStyle(fontSize: 20.0, color: Colors.blue))
                      ])),
                  floatingActionButton: Column(
                      crossAxisAlignment: CrossAxisAlignment.end,
                      mainAxisAlignment: MainAxisAlignment.end,
                      children: <Widget>[
                        FloatingActionButton(
                            heroTag: 'addTag', child: Icon(Icons.add),
                            onPressed: () => BlocProvider.of<NumberBloc>(context).add(NumberEvent.addEvent)),
                        SizedBox(height: 20.0),
                        FloatingActionButton(
                            heroTag: 'removeTag', child: Icon(Icons.remove),
                            onPressed: () => BlocProvider.of<NumberBloc>(context).add(NumberEvent.removeEvent))
                      ]));
            }));
      }
    }
    

    BlocListener

          BlocListenerBlocBuilder 应用有相似之处;其中 listener 用于监听状态变更,可在此做出相应的业务处理;

    class BlocListener<B extends Bloc<dynamic, S>, S> extends BlocListenerBase<B, S> with BlocListenerSingleChildWidget {
      final Widget child;
      const BlocListener({
        Key key,
        @required BlocWidgetListener<S> listener,
        B bloc,
        BlocListenerCondition<S> condition,
        this.child,
      })
    }
    

          简单分析源码可得:

    1. childBlocListener 提供的 Widget 用来响应状态的变更;
    2. blocBlocBuilder 对应的 bloc 用法相同,如果省略了 bloc 参数,BlocListener 将使用 BlocProvider 和当前函数自动执行查找 BuildContext
    3. condition 为可选的过度细粒度,包括两个参数,之前的状态和当前的状态,返回值为 Boolean 类型,true 为进行 listener 的监听,false 时过滤掉 listener 的监听;此时的过滤与 BlocBuilder 中的 condition 过滤无关;
    4. listener 在每次状态变更时调用,其中包括上下文环境和当前状态两个参数;
    @override
    Widget build(BuildContext context) {
      return BlocListener<NumberBloc, int>(
          bloc: _numBloc,
          listener: (context, count) {
            print('BlocPage.listene->$count');
          },
          condition: (previousState, state) {
            print('BlocPage.BlocListener.condition->$previousState==$state');
            return state <= 20 ? true : false;
          },
          child: BlocBuilder<NumberBloc, int>(
              bloc: _numBloc,
              condition: (previousState, state) {
                print('BlocPage.condition->$previousState==$state');
                return state <= 30 ? true : false;
              },
              builder: (context, count) {
                return Scaffold(
                    appBar: AppBar(title: Text('Bloc Page')),
                    body: Center(
                        child: Column( mainAxisAlignment: MainAxisAlignment.center,
                            children: <Widget>[
                          Text('当 Number > 20 时,BlocListener 过滤 listener 监听,与 BlocBuilder 中过滤的状态无关', style: TextStyle(fontSize: 20.0, color: Colors.red)),
                          SizedBox(height: 20.0),
                          Text('当 Number > 30 时,Number 不进行变更', style: TextStyle(fontSize: 20.0, color: Colors.green)),
                          SizedBox(height: 20.0),
                          Text('当前 Number = ${_numBloc.state}', style: TextStyle(fontSize: 20.0, color: Colors.blue))
                        ])),
                    floatingActionButton: Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
                        children: <Widget>[
                          FloatingActionButton(
                              heroTag: 'addTag', child: Icon(Icons.add),
                              onPressed: () => _numBloc.add(NumberEvent.addEvent)),
                          SizedBox(height: 20.0),
                          FloatingActionButton(
                              heroTag: 'removeTag', child: Icon(Icons.remove),
                              onPressed: () => _numBloc.add(NumberEvent.removeEvent))
                        ]));
              }));
    }
    

    TestCode

          小菜在测试过程中遇到一些小问题,仅简单记录一下,以防忘记;

    Q1: There are multiple heroes that share the same tag within a subtree.

          小菜在扩展上一节的 Demo 时,点击进入页面时会黑屏,提示如下错误;

    A1: 在 FloatingActionButton 中添加 heroTag 区分

          以前在学习 Hero Animation 时,在同一个 Page 页面不能用两个相同的 heroTag,小菜这次忽略了 FloatingActionButton 中也应用了 Hero 动画,需要区分一下即可;

    floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
              heroTag: 'addTag', child: Icon(Icons.add),
              onPressed: () => _numBloc.add(NumberEvent.addEvent)),
          FloatingActionButton(
              heroTag: 'removeTag', child: Icon(Icons.remove),
              onPressed: () => _numBloc.add(NumberEvent.removeEvent))
        ]));
    

    Q2: BlocProvider.of() called with a context that does not contain a Bloc of type ...

          小菜在刚开始尝试 BlocProvider.of<NumberBloc>(context) 方式获取 Bloc 时报如下错误;

    A2: 在 build() 外创建或通过如下方式创建,并建议与 BlocBuilder 成对设置

    // build() 方法外创建
    NumberBloc _numBloc;
    @override
    void initState() {
      super.initState();
      _numBloc = NumberBloc();
    }
    
    // BlocProvider create() 创建
    BlocProvider(
      create: (BuildContext context) => NumberBloc(),
      child: ....
    );
    

          小菜刚接触 FlutterBloc 很多高级用法还没涉及到,下一节会尝试多种 Bloc 共同使用的场景,对各方面理解还不到位,如有错误请多多指导!

    来源: 阿策小和尚

    相关文章

      网友评论

        本文标题:Flutter 87: 初识状态管理 Bloc (二)

        本文链接:https://www.haomeiwen.com/subject/vkzaghtx.html