Flutter 本身就有非常强大的状态管理方式 setState,但是如果用于多组件的开发,通过 setState 实现子组件通信是非常麻烦的。
框架信息
状态管理框架 | Pub like | GitHub star | 状态更新 | 共享状态 | 颗粒度 | 说明 |
---|---|---|---|---|---|---|
setState | - | - | Yes | No | - | 适用于较小规模 widget 的暂时性状态的基础管理方法 |
StreamController | - | - | Yes | No | 变量 | 基于流/观察者模式的基础管理方法 |
RxDart | 712 | 2600 | Yes | No | 变量 | 基于流/观察者模式的框架 |
MobX | 224 | 1700 | Yes | No | 变量 | 基于观察及响应的状态管理常用库 |
GetX | 1723 | 1300 | Yes | Yes | 变量 | 轻量级响应式状态管理解决方案 |
Provider | 2488 | 3000 | Yes? | Yes | 类 | 官方推荐的状态共享框架 |
flutter_bloc | 1236 | 5700 | Yes | Yes | 类 | 基于流/观察者模式的框架 |
flutter_redux | 164 | 1300 | Yes | Yes | 前端开发者较为熟悉的状态容器实现。 | |
Fish-Redux | 34 | 6800 | Yes | Yes | 基于 Redux 状态管理的组合式 Flutter 应用框架 |
计数器示例
通过简单的计数器示例对比几个框架的实现,部分框架是不带有状态共享的,所以会配合Provider一起实现。计数器本身是一个非常简单的示例,为了凸显状态管理的意义,我会把“计数器值视图”和“点击按钮”拆分为2个组件,让结构变得复杂一些,就是一个页面加两个组件的方式。
setState
SDK中最简单的更新方式,不同组件之间的通信只能通过构造函数和回调实现。
class CounterPage extends StatefulWidget {
CounterPage({Key key}) : super(key: key);
@override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State {
var counter = 0;
@override
Widget build(BuildContext ct) {
return Scaffold(
body: Center(
child: CounterText(counter),
),
floatingActionButton: Button(
onPressed: () {
setState(() {
counter++;
});
},
),
);
}
}
/// 计数器值视图
class CounterText extends StatelessWidget {
final int count;
CounterText(this.count);
@override
Widget build(BuildContext context) {
return Text('$count');
}
}
/// 点击按钮
class Button extends StatelessWidget {
final VoidCallback onPressed;
Button({@required this.onPressed});
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: onPressed,
child: new Icon(Icons.add),
);
}
}
StreamController+InheritedWidget
这个示例是通过StreamController和InheritedWidget结合从而实现状态更新和状态共享。
/// 用于实现Widget树共享状态状态
class MyProvider extends InheritedWidget {
final dynamic value;
MyProvider({this.value, Widget child}) : super(child: child);
@override
bool updateShouldNotify(covariant MyProvider oldWidget) {
return oldWidget.value != this.value;
}
static T of(BuildContext context) {
MyProvider provider =
context.dependOnInheritedWidgetOfExactType(aspect: MyProvider);
return provider.value;
}
}
class CounterBloc {
StreamController _counterController = StreamController();
int counter = 0;
Stream get counterStream => _counterController.stream;
increment() {
_counterController.add(++counter);
}
dispose() {
_counterController.close();
}
}
class CounterPage extends StatefulWidget {
CounterPage({Key key}) : super(key: key);
@override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State {
var _counterBloc = CounterBloc();
@override
void dispose() {
_counterBloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext ct) {
return MyProvider(
value: _counterBloc,
child: Scaffold(
body: Center(
child: CounterText(),
),
floatingActionButton: Button(),
),
);
}
}
/// 计数器值视图
class CounterText extends StatelessWidget {
@override
Widget build(BuildContext context) {
var bloc = MyProvider.of(context);
return StreamBuilder(
stream: bloc.counterStream,
initialData: bloc.counter,
builder: (context, snapshot) {
return Text('${snapshot.data}');
},
);
}
}
/// 点击按钮
class Button extends StatelessWidget {
@override
Widget build(BuildContext context) {
var bloc = MyProvider.of(context);
return FloatingActionButton(
onPressed: bloc.increment,
child: new Icon(Icons.add),
);
}
}
RxDart+Provider
由 ReactiveX 团队开发的,是一个 dart 语言的 Rx 框架,使用极其轻量级,如果需要共享状态能力建议配合 Provider 一起使用。从下面的示例可以看到,RxDart的用法是接近于 StreamController,其实 BehaviorSubject 就是实现 StreamController,进行了高级的封装,加入了很多简便的用法。由于RxDart不具备状态共享能力,所以结合了Provider实现这个示例。
class CounterBloc {
var counterSubject = BehaviorSubject.seeded(0);
increment() {
counterSubject.value++;
}
dispose() {
counterSubject.close();
}
}
class CounterPage extends StatelessWidget {
CounterPage({Key key}) : super(key: key);
@override
Widget build(BuildContext ct) {
return Provider(
create: (context) => CounterBloc(),
/// 需要手动调动dispose
dispose: (context, bloc) => bloc.dispose(),
child: Scaffold(
body: Center(
child: CounterText(),
),
floatingActionButton: Button(),
),
);
}
}
/// 计数器值视图
class CounterText extends StatelessWidget {
@override
Widget build(BuildContext context) {
var bloc = Provider.of(context);
return StreamBuilder(
stream: bloc.counterSubject,
initialData: bloc.counterSubject.value,
builder: (context, snapshot) {
return Text('${snapshot.data}');
},
);
}
}
/// 点击按钮
class Button extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: (){
Provider.of(context, listen: false).increment();
},
child: new Icon(Icons.add),
);
}
}
dependencies:
rxdart: ^0.24.1
MobX+Provider
flutter_mobx 和其它框架的实现都不太相同,它是基于注解实现,需要配合 mobx_codegen 使用的。需要添加 flutter_mobx、build_runner 和 mobx_codegen 依赖。
dependencies:
flutter_mobx: ^1.1.0+2
dev_dependencies:
build_runner:
mobx_codegen: ^1.1.1+1
创建 counter.dart 文件。
/// counter.dart
import 'package:mobx/mobx.dart';
part 'counter.g.dart';
class CounterStore = _CounterStore with _$CounterStore;
abstract class _CounterStore with Store {
@observable
int counter = 0;
@action
void increment() {
counter++;
}
}
执行命令,就会生成一个 counter.g.dart 文件
flutter packages pub run build_runner build
MobX 的用法很简洁,但是每次修改文件都需要执行 build_runner 命令,如果项目很大,执行时间很长,这个也是一个问题。由于MobX不具备状态共享能力,所以结合了Provider实现这个示例。
class CounterPage extends StatelessWidget {
CounterPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Provider(
create: (_) => CounterStore(),
child: Scaffold(
body: Center(
child: CounterText(),
),
floatingActionButton: Button(),
),
);
}
}
/// 计数器值视图
class CounterText extends StatelessWidget {
@override
Widget build(BuildContext context) {
var store = Provider.of(context);
return Observer(
builder: (context) {
return Text('${store.counter}');
},
);
}
}
/// 点击按钮
class Button extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: (){
Provider.of(context, listen: false).increment();
},
child: new Icon(Icons.add),
);
}
}
#### GetX+Provider
这个框架非常简洁,而且设计的思路和MobX很相似,不同的是,GetX不需要使用 build_runner 技术即可实现相同的效果。不过GetX的功能远不止如此,它提供了入侵性很强的用法,有国际化、主题、路由等功能,功能涉及非常广泛。由于GetX不具备基于Widget树的状态共享能力,所以结合了Provider实现这个示例。
```dart
class CounterController extends GetxController {
var counter = 0.obs;
increment() => counter++;
}
class CounterPage extends StatelessWidget {
CounterPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Provider(
create: (_) => CounterController(),
child: Scaffold(
body: Center(
child: CounterText(),
),
floatingActionButton: Button(),
),
);
}
}
/// 计数器值视图
class CounterText extends StatelessWidget {
@override
Widget build(BuildContext context) {
var controller = Provider.of(context);
return Obx(() => Text('${controller.counter}'));
}
}
/// 点击按钮
class Button extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: (){
Provider.of(context, listen: false).increment();
},
child: new Icon(Icons.add),
);
}
}
```
> dependencies:
> get:
#### Provider
这个框架是 [Google I/O 2019](https://www.youtube.com/watch?v=d_m5csmrf7I) 上推荐的这个框架,并写入了 [Flutter 的官方开发文档](https://flutter.cn/docs/development/data-and-backend/state-mgmt/simple)。这个框架主要是用于不同组件之间实现状态共享,不过它也提供了一个ChangeNotifier 轻量级类,可以用于实现状态更新。
```dart
class CounterModel extends ChangeNotifier {
int counter = 0;
increment() {
counter++;
// 通知视图刷新
notifyListeners();
}
}
class CounterPage extends StatelessWidget {
CounterPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => CounterModel(),
child: Scaffold(
body: Center(
child: CounterText(),
),
floatingActionButton: Button(),
),
);
}
}
/// 计数器值视图
class CounterText extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer(builder: (context, counterModel, child) {
return Text('${counterModel.counter}');
});
}
}
/// 点击按钮
class Button extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: () {
Provider.of(context, listen: false).increment();
},
child: new Icon(Icons.add),
);
}
}
```
> dependencies:
> provider: ^4.3.2+2
bloc
bloc 的用法和 Provider 很相似,也提供不同组件之间实现状态共享,也提供了状态更新功能。
class CounterCubit extends Cubit {
CounterCubit() : super(0);
void increment() => emit(state + 1);
}
class CounterPage extends StatelessWidget {
CounterPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterCubit(),
child: Scaffold(
body: Center(
child: CounterText(),
),
floatingActionButton: Button(),
),
);
}
}
/// 计数器值视图
class CounterText extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder(builder: (context, count) {
return Text('$count');
});
}
}
/// 点击按钮
class Button extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: () {
context.bloc().increment();
},
child: new Icon(Icons.add),
);
}
}
Redux
这个相比以上的框架,是复杂一些的,其思想源于前端开发。
enum Actions { Increment }
int counterReducer(int state, dynamic action) {
if (action == Actions.Increment) {
return state + 1;
}
return state;
}
class CounterPage extends StatelessWidget {
CounterPage({Key key}) : super(key: key);
final store = new Store(counterReducer, initialState: 0);
@override
Widget build(BuildContext context) {
return StoreProvider(
store: store,
child: Scaffold(
body: Center(
child: CounterText(),
),
floatingActionButton: Button(),
),
);
}
}
/// 计数器值视图
class CounterText extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StoreConnector(
converter: (store) => store.state.toString(),
builder: (context, count) {
return Text(count);
},
);
}
}
/// 点击按钮
class Button extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new StoreConnector(
converter: (store) {
return () => store.dispatch(Actions.Increment);
},
builder: (context, callback) {
return new FloatingActionButton(
onPressed: callback,
child: new Icon(Icons.add),
);
},
);
}
}
fish_redux
闲鱼开源基于Redux思想的框架,但是比官方Redux要复杂很多。这个项目虽然 star 很多,但是半年没有更新了,还处于 0.3.4 版本,被提了130多个 Issue,也没有人去处理,估计闲鱼团队自己都已经放弃这个框架了吧,建议不要使用这个框架。
总结
状态管理的框架很多,Redux的思想和其它的都不太一样,由于我对Redux的思想没有足够深入的了解,这里就不做更多的评价。Flutter还是推荐BLoC思想的,基于颗粒度主要是分为2类,“变量”和“类”。如果需要涉及很多状态的改变,如果颗粒度为“变量”,每一个状态只需要创建一个变量即可,但是也有一个缺点,需要搭配Provider一起使用,自动管理能力较差。对于颗粒度为“类”的框架,要么就是牺牲更新的颗粒度,定义一个包含很多状态的State,缺点就是无法做到小颗粒更新,一个小小的改变,所有依赖这个State的地方都需要刷新。要么就只能为每一个状态创建一个class,有多少状态就有多少个class,缺点就是整体代码量大大增加。我个人的偏向是使用颗粒度为变量的RxDart框架,搭配Provider,使用更加灵活。 虽然 MobX 更加优雅,但是 MobX 是基于注解,业务层本身就经常需要修改,用起来成本较高。GetX 确实是最优雅的,值得去研究。