• Flutter 状态管理之Bloc


    前言:Flutter 的状态管理插件有很多,比如 ProviderGetX 还有本篇要讲述的 Bloc 。Bloc 目前最新的版本是 flutter_bloc: ^8.0.1

    BLoC 依赖 Stream和 StreamController实现,组件通过Sinks发送更新状态的事件,然后再通过 Streams通知其他组件更新。事件处理和通知刷新的业务逻辑都是由 BLoC 完成,从而实现业务逻辑与 UI 层的分离,并且逻辑部分可以做到复用。

    在这里插入图片描述

    之前我们更新数据通常是通过 setState 的方式实现的,这种会刷新整个页面,而使用 Bloc 只会刷新想要更新的UI部分。下面会通过几个例子来说明下。

    一、使用 Bloc 来实现计数器且把数据传递给跳转的页面

    计算器要实现加减一的功能,所以先定义2个 Event,且都继承 CounterEvent ,如下:

    // 定义 event 的基类
    abstract class CounterEvent {}
    // 加1的 event
    class IncrementEvent extends CounterEvent {}
    // 减1的event
    class DecrementEvent extends CounterEvent {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后定义一个 CounterBloc 如下:

    /// 表示通过 Bloc 发送的事件只能是 CounterEvent, 返回值是 int
    class CounterBloc extends Bloc<CounterEvent, int> {
      CounterBloc(int initialState) : super(initialState) {
        /// 减1的事件就是把当前的 state - 1
        on<DecrementEvent>((event, emit) {
          /// 把对应的状态发送出去,在页面中就可以通过 BlocBuilder 来观察数据的改变
          emit(state - 1);
        });
        /// 加1的事件就是把当前的 state + 1
        on<IncrementEvent>((event, emit) {
          emit(state + 1);
        });
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    页面具体的代码如下:

    void main() {
      runApp(MyApp());
    }
    
    • 1
    • 2
    • 3
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          /// 要使用 BlocProvider 来提供 Bloc 
          home: BlocProvider<CounterBloc>(
            create: (BuildContext context) {
              return CounterBloc(0);
            },
            child: CounterPage(),
          ),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    class CounterPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context);
        print(
            "---------------------- CounterPage build ----------------------- ${counterBloc.hashCode}");
        return Scaffold(
          appBar: AppBar(
            title: Text('test bloc '),
          ),
          body: Container(
            width: double.infinity,
            child: Column(
              children: [
                /// 当增加或者减少计数时,只会局部更新 BlocBuilder ,不会整个刷新 build
                BlocBuilder<CounterBloc, int>(
                  builder: (BuildContext context, int count) {
                    print(
                        "---------------------- BlocBuilder build -----------------------");
                    return Text(
                      '当前计数: $count',
                      style: TextStyle(fontSize: 24),
                    );
                  },
                  buildWhen: (previous, next) {
                    /// 这样写只有 increment 才有用,用来控制触发刷新的逻辑
                    return previous < next;
                  },
                ),
    
                SizedBox(
                  height: 12,
                ),
                ElevatedButton(
                  onPressed: () {
                    counterBloc.add(IncrementEvent());
                  },
                  child: Text(
                    'increment',
                  ),
                ),
                ElevatedButton(
                  onPressed: () {
                    counterBloc.add(DecrementEvent());
                  },
                  child: Text(
                    'decrement',
                  ),
                ),
                ElevatedButton(
                  onPressed: () {
                    /// 需要 BlocProvider 的 context , 且 BlocProvider 的  create 中返回当前的 counterBloc
                    /// 如果你在 create 中 重新 new 一个 CounterBloc ,那么在 page2 中增加计数,不会刷新本页面的计数
                    Navigator.push(context, MaterialPageRoute(builder: (context) {
                      return BlocProvider<CounterBloc>(
                        create: (BuildContext context) {
                          return counterBloc;
                        },
                        child: BlocPage2(),
                      );
                    }));
                  },
                  child: Text(
                    'jump page 2',
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    其中的 buildWhen 是过滤触发条件的,代码中有注释了。BlocPage2 的代码如下:

    class BlocPage2 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context);
        print('xxxxxxxxxxxxxxx ${counterBloc.hashCode} ');
    
        return Scaffold(
          appBar: AppBar(
            title: Text('BlocPage2'),
          ),
          body: Container(
            width: double.infinity,
            child: Column(
              children: [
                BlocBuilder<CounterBloc, int>(
                    builder: (BuildContext context, int count) {
                  return Text(
                    'page2 当前计数: $count',
                    style: TextStyle(fontSize: 24),
                  );
                }),
                ElevatedButton(
                    onPressed: () {
                      counterBloc.add(IncrementEvent());
                    },
                    child: Text('add')),
              ],
            ),
          ),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    BlocPage2 中改变了计数,返回到 CounterPage 页面时计数会同步更新。

    结语:本篇这里就结束了,下篇会通过一个真正的例子来说明 Bloc 是怎么做到 UI 和业务逻辑分离的,会有真正的网络请求和页面的刷新。

  • 相关阅读:
    电脑出现msvcp110.dll丢失的解决方法,快速解决msvcp110.dll丢失
    Opencv学习项目3——pytesseract
    Replication(上):常见复制模型&分布式系统挑战
    Python 教程之如何使用 Streamlit 在 Python 中编写 SaaS(软件即服务)Web 应用程序
    华为云平台零代码搭建物联网可视化大屏体验:疫情防控数据大屏
    5 评价类算法:CRITIC法笔记(附Python代码)
    Canmv K210开发板案例——人脸识别
    从0开始学Java:Java概述
    华为云云耀云服务器L实例评测|企业项目最佳实践之私有库搭建verdaccio(八)
    测试开发都这么厉害了?为啥不直接转业务开发?
  • 原文地址:https://blog.csdn.net/zhujiangtaotaise/article/details/126152421