写点什么

为了弄懂 Flutter 的状态管理, 我用 10 种方法改造了 counter app

用户头像
Android架构
关注
发布于: 9 小时前

BLoC 模式的全称是: business logic component.


所有的交互都是 a stream of asynchronous events.


Widgets + Streams = Reactive.


BLoC 的实现的主要思路: Events in -> BloC -> State out.


Google I/O 2018 上推荐的还是这个, 2019 就推荐 Provider 了.


当然也不是说这个模式不好, 架构模式本来也没有对错之分, 只是技术选型不同.

BLoC 手动实现

不添加任何依赖可以手动实现 BLoC, 利用:


  • Dart SDK > dart:async > Stream.

  • Flutter 的StreamBuilder: 输入是一个 stream, 有一个 builder 方法, 每次 stream 中有新值, 就会 rebuild.


可以有多个 stream, UI 只在自己感兴趣的信息发生变化的时候重建.


BLoC 中:


  • 输入事件: Sink<Event> input.

  • 输出数据: Stream<Data> output.


CounterBloc 类:


class CounterBloc {


int _counter = 0;


final _counterStateController = StreamController<int>();


StreamSink<int> get _inCounter => _counterStateController.sink;


Stream<int> get counter => _counterStateController.stream;


final _counterEventController = StreamController<CounterEvent>();


Sink<CounterEvent> get counterEventSink => _counterEventController.sink;


CounterBloc() {


_counterEventController.stream.listen(_mapEventToState);


}


void _mapEventToState(CounterEvent event) {


if (event is IncrementEvent) {


_counter++;


} else if (event is DecrementEvent) {


_counter--;


}


_inCounter.add(_counter);


}


void dispose() {


_counterStateController.close();


_counterEventController.close();


}


}


有两个StreamController, 一个控制 state, 一个控制 event.


读取状态值要用StreamBuilder:


StreamBuilder(


stream: _bloc.counter,


initialData: 0,


builder: (BuildContext context, AsyncSnapshot<int> snapshot) {


return Text(


'${snapshot.data}',


);


},


)


而改变状态是发送事件:


FloatingActionButton(


onPressed: () => _bloc.counterEventSink.add(IncrementEvent()),


),


实现细节:


  • 每个屏幕有自己的 BLoC.

  • 每个 BLoC 必须有自己的dispose()方法. -> BLoC 必须和StatefulWidget一起使用, 利用其生命周期释放.


代码分支: [bloc](


)

BLoC 传递: 用 InheritedWidget

手动实现的 BLoC 模式, 可以结合InheritedWidget, 写一个 Provider, 用来做 BLoC 的传递.


代码分支: [bloc-with-provider](


)

BLoC rxdart 实现

用了 rxdart package 之后, bloc 模块的实现可以这样写:


class CounterBloc {


int _counter = 0;


final _counterSubject = BehaviorSubject<int>();


Stream<int> get counter => _counterSubject.stream;


final _counterEventController = StreamController<CounterEvent>();


Sink<CounterEvent> get counterEventSink => _counterEventController.sink;


CounterBloc() {


_counterEventController.stream.listen(_mapEventToState);


}


void _mapEventToState(CounterEvent event) {


if (event is IncrementEvent) {


_counter++;


} else if (event is DecrementEvent) {


_counter--;


}


_counterSubject.add(_counter);


}


void dispose() {


_counterSubject.close();


_counterEventController.close();


}


}


BehaviorSubject也是一种StreamController, 它会记住自己最新的值, 每次注册监听, 会立即给你最新的值.


代码分支: [bloc-rxdart](


).

BLoC Library

可以用这个 package 来帮我们简化代码: [https://pub.dev/packages/flutter_bloc](


)


自己只需要定义 Event 和 State 的类型并传入, 再写一个逻辑转化的方法:


class CounterBloc extends Bloc<CounterEvent, CounterState> {


@override


CounterState get initialState => CounterState.initial();


@override


Stream<CounterState> mapEventToState(CounterEvent event) async* {


if (event is IncrementEvent) {


yield CounterState(counter: state.counter + 1);


} else if (event is DecrementEvent) {


yield CounterState(counter: state.counter - 1);


}


}


}


BlocProvider来做 bloc 的传递, 从而不用在构造函数中一传到底.


访问的时候用BlocBuilderBlocProvider.of<CounterBloc>(context).


BlocBuilder(


bloc: BlocProvider.of<CounterBloc>(context),


builder: (BuildContext context, CounterState state) {


return Text(


'${state.counter}',


);


},


),


这里 bloc 参数如果没有指定, 会自动向上寻找.


BlocBuilder有一个参数condition, 是一个返回 bool 的函数, 用来精细控制是否需要 rebuild.


FloatingActionButton(


onPressed: () =>


BlocProvider.of<CounterBloc>(context).add(IncrementEvent()),


),


代码分支: [bloc-library](


).


rxdart




这是个原始版本的流式处理.


和 BLoC 相比, 没有专门的逻辑模块, 只是改变了数据的形式.


利用 rxdart, 把数据做成流:


class CounterModel {


BehaviorSubject _counter = BehaviorSubject.seeded(0);


get stream$ => _counter.stream;


int get current => _counter.value;


increment() {


_counter.add(current + 1);


}


decrement() {


_counter.add(current - 1);


}


}


获取数据用StreamBuilder, 包围的范围尽量小.


StreamBuilder(


stream: counterModel.stream$,


builder: (BuildContext context, AsyncSnapshot snapshot) {


return Text(


'${snapshot.data}',


);


},


),


Widget dispose 的时候会自动解绑.


数据传递的部分还需要进一步处理.


代码分支: [rxdart](


).


Redux




Redux 是前端流行的, 一种单向数据流架构.


概念:


  • Store: 用于存储State对象, 代表整个应用的状态.

  • Action: 事件操作.

  • Reducer: 用于处理和分发事件的方法, 根据收到的Action, 用一个新的State来更新Store.

  • View: 每次 Store 接到新的 State, View就会重建.


Reducer是唯一的逻辑处理部分, 它的输入是当前StateAction, 输出是一个新的State.

Flutter Redux 状态管理实现

首先定义好 action, state:


enum Actions {


Increment,


Decrement,


}


class CounterState {


int _counter;


int get counter => _counter;


CounterState(this._counter);


}


reducer 方法根据 action 和当前 state 产生新的 state:


CounterState reducer(CounterState prev, dynamic action) {


if (action == Actions.Increment) {


return new CounterState(prev.counter + 1);


} else if (action == Actions.Decrement) {


return new CounterState(prev.counter - 1);


} else {


return prev;


}


}


  • 数据提供者: StoreProvider.


放在上层:


StoreProvider(


store: store,


child: MaterialApp(


title: 'Flutter Demo',


theme: ThemeData(


primarySwatch: Colors.blue,


),


home: MyHomePage(title: 'Flutter Demo Home Page'),


),


);


  • 数据消费者: StoreConnector, 可读可写.


读状态:


StoreConnector<CounterState, String>(


converter: (store) => store.state.counter.toString(),


builder: (context, count) {


return Text(


'$count',


);


},


)


改变状态: 发送 action:


StoreConnector<CounterState, VoidCallback>(


converter: (store) {


return () => store.dispatch(action.Actions.Increment);


},


builder: (context, callback) {


return FloatingActionButton(


onPressed: callback,


);


},


),


代码分支: [redux](


).


MobX




MobX 本来是一个 JavaScript 的状态管理库, 它迁移到 dart 的版本: [mobxjs/mobx.dart](


).


核心概念:


  • Observables

  • Actions

  • Reactions

MobX 状态管理实现

官网提供了一个 counter 的指导: [https://mobx.netlify.com/getting-started](


)


这个库的实现需要先生成一些代码.


先写类:


import 'package:mobx/mobx.dart';


part 'counter.g.dart';


class Counter = _Counter with _$Counter;


abstract class _Counter with Store {


@observable


int value = 0;


@action


void increment() {


value++;


}


@action


void decrement() {


value--;


}


}


运行命令flutter packages pub run build_runner build, 生成counter.g.dart.


改完之后就不需要再使用StatefulWidget了.


找一个合适的地方初始化数据对象并保存:


final counter = Counter();


读取值的地方用Observer包裹:


Observer(


builder: (_) => Text(


'${counter.value}',


style


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


: Theme.of(context).textTheme.display1,


),


),


改变值的地方:


FloatingActionButton(


onPressed: counter.increment,


tooltip: 'Increment',


child: Icon(Icons.add),


),


代码分支: [mobx](


).


Flutter hooks




React hooks 的 Flutter 实现.


package: [https://pub.dev/packages/flutter_hooks](


)


Hooks 存在的目的是为了增加 widgets 之间的代码共享, 取代StatefulWidget.


首页的例子是: 对一个使用了AnimationControllerStatefulWidget的简化.


flutter_hooks 包中已经内置了一些已经写好的 hooks.

Flutter hooks useState

counter demo 一个最简单的改法, 就是将StatefulWidget改为HookWidget.


build方法里:


final counter = useState(0);


调用useState方法设定一个变量, 并设定初始值, 每次值改变的时候 widget 会被 rebuild.


使用值:


Text(


'${counter.value}',


),


改变值:


FloatingActionButton(


onPressed: () => counter.value++,


),


实际上是把StatefulWidget包装了一下, 在初始化 Hook 的时候注册了 listener, 数据改变的时候调用setState()方法.


只是把这些操作藏在 hook 里, 不需要开发者手动调用而已.


所以本质上还是StatefulWidget, 之前解决不了的问题它依然解决不了.


代码分支: [flutter-hooks](


).


Demo




本文 demo 地址: [https://github.com/mengdd/counter_state_management](


)


每个分支对应一种实现. 切换不同分支查看不同的状态管理方法.


对于代码的说明:


这是 counter app 用不同的状态管理模式进行的改造.


因为这个 demo 的逻辑和 UI 都比较简单, 可能实际上并不需要用上一些复杂的状态管理方法, 有种杀鸡用牛刀的感觉.


只是为了保持简单来突出状态管理的实现, 说明用法.


一些自己的感想




老实说, 做了这么多年 Android, 各种构架 MVP, MVVM, MVI, 目的就是数据和逻辑分离, 逻辑和 UI 分离,


所以初识 Flutter 的时候对这种万物皆 widget, 一个树里面包含一切的方式有点怀疑, UI 逻辑数据写成一堆, 程序功能复杂后, 肯定会越写越乱.


但是了解了它的状态管理之后, 发现 Flutter 的状态管理就是它的程序构架, 并且也是百家争鸣各取所需.


只是 Flutter 的构架是服务于 Flutter framework 的设计思想的, 要遵从利用它, 而不是与之反抗.


爱它如是, 而不是如我所愿.


印证了一些道理:


  • 不要只喜欢自己熟悉的东西.

  • 了解之后才有发言权.


参考


--


  • [Flutter 官方文档](


)


  • [Flutter 官方文档 options](


)


  • [Flutter Architecture Samples](


)


  • [Flutter State Management - The Grand Tour](


)

Google I/O

  • [Build reactive mobile apps with Flutter (Google I/O'18)](


)


  • [Pragmatic State Management in Flutter (Google I/O'19)](


)

InheritedWidget

  • [InheritedWidget](


)


  • [Flutter 实战 7.2 数据共享(InheritedWidget)](


)

Scoped Model

  • [scoped_model package](


)

provider

  • [Flutter guide](


)


  • [Flutter samples: provider shopper](


)


  • [Flutter 实战 7.3 跨组件状态共享(Provider)](


)

Bloc

  • [Build reactive mobile apps in Flutter — companion article](


)


  • [filiph/state_experiments](


)


  • [Flutter BLoC Pattern](


)


  • [Getting Started with the BLoC Pattern](


)


  • [Effective BLoC pattern](


)

Redux

  • [Introduction to Redux in Flutter](


)


  • [flutter redux package](


)


  • [flutter redux github](


)

MobX

  • [mobx github](


)

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
为了弄懂Flutter的状态管理, 我用10种方法改造了counter app