Flutter是声明式编程模式,开发者就像堆积木一样来堆模型,只用关心每块积木展示的条件,不需要关心积木内部的功能实现细节。因此每一块积木都是一种状态,慢慢随着积木越来越多开发者必须要把这些状态(形态、关系)管理好,否则就会是一团麻了。
编程语言主要有三种类型:
- 声明式编程:专注于”做什么”而不是”如何去做”。在更高层面写代码,更关心的是目标,而不是底层算法实现的过程。
如:css, 正则表达式,sql 语句,html, xml… - 命令式编程(过程式编程) : 专注于”如何去做”,这样不管”做什么”,都会按照你的命令去做。解决某一问题的具体算法实现。
- 函数式编程:把运算过程尽量写成一系列嵌套的函数调用。
iOS 和 Android 的原生开发模式是命令式编程模式。命令式编程要求开发者一步步描述整个构建过程,从而引导程序去构建用户界面。
常见状态管理器对比
状态管理器一般会关注两点:
- 状态的更新
- 状态的共享
以下是一些常见的状态管理对比
状态管理框架 | 状态更新 | 状态共享 | 说明 |
---|---|---|---|
setState | yes | no | 自带的状态管理器,适用于较小规模 widget 的暂时性状态的基础管理方法 |
StreamController | yes | no | 基于流/观察者模式的基础管理方法 |
RxDart | yes | no | 基于流/观察者模式的框架,对StreamController进行了高级封装 |
flutter_bloc | yes | yes | 底层基于 InheritedWidget,核心思想也是基于流来管理数据 |
Provider | yes | yes | 基于 InheritedWidget 和 ChangeNotifier 进行了封装,使用缓存提升性能,避免不必要的重绘 |
Fish-Redux | yes | yes | 闲鱼出品,基于前端Redux思想,似乎不再维护 |
GetX | yes | yes | 轻量级响应式状态管理解决方案,目前很受欢迎 |
InheritedWidget
系统自带的数据共享组件,例如我们在应用根 widget 中通过InheritedWidget共享了一个数据,那么我们便可以在任意子 widget 中来获取该共享的数据。
创建方式:
class MyInheritedWidget extends InheritedWidget {
final int data;
/// 在构造方法中,我们需要传入两个参数:
/// 一个是我们希望共享的数据(在本例中数据是int型,实际业务中共享的通常是一个相对复杂的数据),
/// 另一个就是我们带界面的Widget。
MyInheritedWidget(@required this.data, Widget child) : super(child: child);
/// 获取数据方法
static MyInheritedWidget getData(BuildContext context) {
return context.inheritFromWidgetOfExactType(MyInheritedWidget);
}
/// 决定通知子节点中StatefulWidget的didChangeDependencies方法是否调用。
/// StatefulWidget的didChangeDependencies方法就是与InheritedWidget配合使用的。只有当InheritedWidget发生更新并且决定通知时,didChangeDependencies才会调用。
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
return oldWidget.data != data;
}
}
使用场景:
@override
Widget build(BuildContext context) {
return Text(
'count : ${MyInheritedWidget.getData(context).data}'
);
}
StreamController
RxDart、BloC、flutter_bloc 都是基于 Stream 开发,Stream 的思想是基于管道(pipe)和 生产者消费者模式。
Stream并是不Flutter的产物,而是由Dart提供的,Stream是一个抽象的接口,Dart提供了StreamController接口类可以让我们方便的使用Stream。步骤大致如下:
- 创建StreamController
- 获取StreamSink,用作发射事件
- 获取Stream流,可用作事件的监听
- 获取StreamSubscription,用作管理监听、关闭、暂停等
//创建StreamController
StreamController<String> streamController = StreamController<String>();
// 获取StreamSink用于发射事件
StreamSink<String> get streamSink => streamController.sink;
// 获取Stream用于监听
Stream<String> get streamData => streamController.stream;
//发射一个事件.
streamSink.add(index.toString());
//监听事件,返回StreamSubscription,可用于监听的取消
StreamSubscription<String> subscription = streamData.listen((value) {
// do something
});
StreamBuilder
StreamBuilder对Stream相关逻辑的包装组件(Widget),StreamBuilder 内部已经帮我们完成了stream的订阅与取消订阅,因此在Widget中监听数据变化及时刷新界面更方便。
构造方法:
StreamBuilder({Key key, T initialData, Stream<T> stream, @required AsyncWidgetBuilder<T> builder })
- initialData : 默认初始化数据
- stream : stream事件流对象
- builder : 接收两个参数
BuildContext context
和AsyncSnapshot<T> snapshot
, 返回值是 Widget 组件 ;AsyncSnapshot<T> snapshot
参数中包含有异步计算的信息;
其他方法:
- afterConnected:返回一个AsyncSnapshot,当订阅了stream时会回调此AsyncSnapshot
- afterData:返回一个AsyncSnapshot,当stream有事件触发时会回调此AsyncSnapshot
- afterDisconnected:返回一个AsyncSnapshot,当取消订阅stream时会回调此AsyncSnapshot
- afterDone:返回一个AsyncSnapshot,当stream被关闭时会回调此AsyncSnapshot
- afterError:返回一个AsyncSnapshot,stream发生错误时会回调此AsyncSnapshot
接收数据:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('streamBuilder')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
StreamBuilder<String>(
stream: streamData,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
return Text('Result: ${snapshot.data}');
}
)
],
)
)
);
}
Provider
8种内容提供者,常用的有:
- MultiProvider,多种复合嵌套,不建议入口处同时初始化多个provider,容易引起内存瞬间加大,除了一些特殊场景,例如日夜间切换,和语言切换。
- ChangeNotifyProvider, 使用时继承 ChangeNotifier即可。
provider 实例化及取值也有三种:
- XXXProvider provider = Provider.of(context);
- 采用Consume
- 调用Provider的方法:context.read().xxx();
观察Provider的数据,用于展示:context.watch().xxx;
实例:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
/// 1 创建ChangeNotifier,实际上就是我们的状态,它不仅存储了我们的数据模型,还包含了更改数据的方法,并暴露出它想要暴露出的数据
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// 2 ChangeNotifierProvider. 在Widget Tree中插入ChangeNotifierProvider,以便Consumer可以获取到数据
///
/// 创建顶层共享数据。这里使用MultiProvider可以创建多个共享数据,因为实际的应用不可能只有一个数据模型
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
],
child: MaterialApp(
title: 'Flutter Demo',
home: FirstPage(),
),
);
}
}
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Provider练习")),
body: Center(
/// 3 获取Provider实例
/// ctx: context,上下文,目的是知道当前树的对象
/// counterPro: ChangeNotifier对应的实例,也是我们在builder函数中主要使用的对象
/// child: 目的是进行优化,如果builder下面有一颗庞大的子树,当模型发生改变的时候,我们并不希望重新build这颗子树,那么就可以将这颗子树放到Consumer的child中,在这里直接引入即可.
child: Consumer<Counter>(builder: (ctx, counterPro, child) {
return Text(
"当前计数:${counterPro.count}",
style: TextStyle(fontSize: 20, color: Colors.red),
);
}),
),
floatingActionButton: Consumer<Counter>(
builder: (ctx, counterPro, child) {
return FloatingActionButton(
child: child,
onPressed: () {
counterPro.increment();
},
);
},
child: Icon(Icons.add), //! Icon放在builder外面,防止每次跟着一起刷新
),
);
}
}
GetX
GetX相比Provider更灵活,轻量,简单,api也更丰富,除了包含状态管理,还包含路由配置,另外不在依赖Context,全局随便调用。
快速上手
- 入口,将MaterialApp改成GetMaterialApp
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// GetMaterialApp重写了MaterialApp,可以配置很多,如,主题、国际化、静态路由等
return GetMaterialApp(
home: SimplePage(),
);
}
}
- 创建GetxController,用于管理数据和刷新操作
class SimpleController extends GetxController {
int _counter = 0;
int get counter => _counter;
void increment() {
_counter++;
// 刷新(GetxController通过update()更新GetBuilder)
update();
}
}
- 采用GetBuild构建视图,监听数据变化
class SimplePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('SimplePage--build');
return GetBuilder<SimpleController>(
init: SimpleController(),
builder: (controller) {
return Scaffold(
appBar: AppBar(title: Text('Simple')),
body: Center(
child: Text(controller.counter.toString()),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.increment();
},
child: Icon(Icons.add),
),
);
});
}
}
class SimplePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Simple')),
body: Center(
child: GetBuilder<SimpleController>(
init: SimpleController(),
// 每次执行update,builder内部都会重新构建
builder: (controller) {
return Text(controller.counter.toString());
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 找到Controller执行操作
Get.find<SimpleController>().increment();
},
child: Icon(Icons.add),
),
);
}
}
注:GetBuilder用于小部件的刷新,不建议放到根节点上全局刷。
局部刷新
update()
使所有绑定了该Controller的GetBuilder都执行了刷新,可通过追加id,进行局部刷新:
Controller:
int _counter = 0;
int get counter => _counter;
String _name = "Lili";
String get firstName => _name;
void increment() {
_counter++;
_name = WordPair.random().asPascalCase;
update(['counter']);
}
void changeName() {
_counter++;
_name = WordPair.random().asPascalCase;
update(['name']);
}
GetBuilder:
GetBuilder<SimpleAdvancedController>(
id: 'counter',
builder: (ctl) => Text(ctl.counter.toString()),
),
SizedBox(
height: 50,
),
GetBuilder<SimpleAdvancedController>(
id: 'name',
builder: (ctl) => Text(ctl.firstName),
),
GetxController生命周期
class SimpleController extends GetxController {
int _counter = 0;
int get counter => _counter;
void increment() {
_counter++;
update();
}
@override
void onInit() {
super.onInit();
print('SimpleController--onInit');
}
@override
void onReady() {
super.onReady();
print('SimpleController--onReady');
}
@override
void onClose() {
super.onClose();
print('SimpleController--onClose');
}
}
响应式刷新
在Controller的变量后追加.obs
创建响应式数据,使得该变量变成了可观察者:
var name = '新垣结衣'.obs;
观察变量的改变,不在需要update,不在是GetBuilder了:
Obx (() => Text (controller.name));
因为是响应式,只要数据改变,界面布局自动刷新。
细心的同学会发现.obs
的底层是Rx包装,所以也可以用Rx来定义响应式变量,这里不在说了。
跨路由使用
不同页面跨路由使用,只需要把controller存储起来即可:
class CrossOnePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// Get.put存储controller
CrossOneController controller = Get.put(CrossOneController());
...
}}
另外一个页面使用时,获取controller:
CheetahButton('打印CrossOneController的age', () {
/// Get.find获取controller
print(Get.find<CrossOneController>().age);
}),
此时需要注意一个问题:
- 当
Get.put
在build里put时,controller的生命周期和该widget保持一致的,widget销毁了这个controller也自动销毁了 - 等
Get.put
作为成员变量在build外部执行时,controller的生命周期是和所属widget的引用保持一致的。
我们开发过程中总喜欢一个页面对应一个controller,如果作为成员变量存放,这样的话需要额外注意内存的回收问题,可以建一个Widget,来管理controller的回收。
其他
路由、弹框需自行进一步挖掘。