状况办理
官方介绍地址:State management
主流状况办理,官方引荐地址:状况办理参阅
Flutter的 状况办理 跟 反应式运用程序中的状况办理 很像。状况办理的效果是办理组件中需求重建的数据。
状况: 当任何时分需求重建用户界面时所需求的数据,该数据能够称为页面的状况。
Android : 命令式结构中修正 UI
ViewB b = new ViewB(context)
b.setColor(red)
b.clearChildren()
ViewC c3 = new ViewC(...)
b.add(c3)
Flutter:声明式修正UI
return ViewB(
color: red,
child: ViewC(...),
)
当用户界面发生改变时,Flutter 不会修正旧的实例 b,而是构造新的 widget 实例。结构运用 RenderObjects 办理。 RenderObjects 在帧之间保持不变, Flutter 的轻量级 widget 告诉结构在状况之间修正 RenderObjects, Flutter 结构则处理其余部分。
Ephemeral & App State
需求自己 办理 的状况能够分为两种概念类型:短时 (ephemeral) 状况和运用 (app) 状况。
短时状况:
有时也称 用户界面 (UI) 状况 或者 **局部状况,**是能够彻底包含在一个独立 widget 中的状况。
这种情况不需求运用状况办理架构(例如 ScopedModel, Redux)去办理这种状况,需求用的只是一个 StatefulWidget。
运用状况:
运用中的多个部分之间同享一个非短时的状况,而且在用户会话期间保存这个状况,称之为运用状况(有时也称同享状况)。
页面状况办理现状:
现在项目中首要运用两种方案处理页面状况办理:
-
Flutter 原生的 setState
-
十分的一个Flutter官方引荐的开源Provider 库
setState:
-
需求结合 StatefullWidget 运用
-
短时状况经常被用于一个单独 widget 的本地状况,一般运用 State 和 setState() 来完成。
setState 问题:
-
改写是大范围的全体改写,而不是精密的改写
-
只有 StatefullWidget 即有状况的 widget,才支撑 setState 办法进行改写
setState 长处:
-
官方供给,运用极简略
-
调用无需context
provider:
工程结构:
Flutter 官方引荐,provider 十分好理解而且不需求写许多代码。
底层是运用了InheritedWidget, InheritedNotifier, InheritedModel等才能。InheritedWidget是Flutter中特别重要的一个Widget,它供给了一种数据在widget树中从上到下传递、同享的办法。
provider 中心类
-
ChangeNotifier
-
ChangeNotifierProvider
-
Consumer
ChangeNotifier:
它用于向监听器发送告诉。换言之,假如被定义为 ChangeNotifier,你能够订阅它的状况改变。
ChangeNotifierProvider:
ChangeNotifierProvider widget 能够向其子孙节点暴露一个 ChangeNotifier 实例。
Consumer:
当承继于ChangeNotifier 子类 经过 ChangeNotifierProvider 在运用中与 widget 相关联,则能够运用 Consumer widget。
有必要指定要拜访的模型类型。在这个示例中,要拜访 CartModel 那么就写上 Consumer<CartModel>。
Consumer widget 仅有有必要的参数就是 builder。当 ChangeNotifier 发生改变的时分会调用 builder 这个函数。(换言之,当调用 notifyListeners() 时,所有相关的 Consumer widget 的 builder 办法都会被调用。)
Provider.of
运用 Provider.of,而且将 listen 设置为 false。用于拜访数据,但又不希望改变ui的场景。
provider 问题:
-
需求context 强绑定
-
不能改写屏幕外页面
-
不支撑呼应式变成
provider 长处:
-
运用简略,大约2天内掌握运用。
-
调试方便
其他:根据provider的页面多状况模板代码规划
一个网络页面,加载网络数据可能分为:加载中、恳求成功、恳求失败、暂无数据状况,而这些彻底能够模板代码的办法进行供给。
对应 widget 模板代码:
class EkLoadWidget<P extends EkPageLoadProvider> extends StatelessWidget {
final Widget Function(BuildContext context) success;
final Widget Function(BuildContext context) failed;
final Widget Function(BuildContext context) netFailed;
final Widget Function(BuildContext context) noData;
final Widget Function(BuildContext context) loading;
EkLoadWidget({this.success, this.failed, this.netFailed, this.noData, this.loading});
@override
Widget build(BuildContext context) {
return SafeArea(
child: Selector<P, EkPageState>(
selector: (context, provider) => provider.pageStatus,
builder: (_, pageStatus, __) {
switch (pageStatus) {
case EkPageState.SUCCESS:
return _buildLoadSuccess(context);
case EkPageState.FAILED:
return _buildLoadFailed(context);
case EkPageState.NET_FAILED:
return _buildLoadNetFailed(context);
case EkPageState.NO_DATA:
return _buildLoadNoData(context);
default:
return _buildLoading(context);
}
},
));
}
Widget _buildLoadSuccess(BuildContext context) {
if (success == null) return Container();
return success(context);
}
Widget _buildLoadFailed(BuildContext context) {
if (failed == null) return Container();
return failed(context);
}
Widget _buildLoadNetFailed(BuildContext context) {
if (netFailed == null) return Container();
return netFailed(context);
}
Widget _buildLoadNoData(BuildContext context) {
if (noData == null) return Container();
return noData(context);
}
Widget _buildLoading(BuildContext context) {
if (loading == null)
return Center(
child: CircularProgressIndicator(),
);
return loading(context);
}
}
运用方只需求传参 success、failed、netFailed、noData、loading 即可。
对应 provider 模板代码:
class EkPageLoadProvider extends EkChangeNotifier {
EkPageState _pageStatus = EkPageState.LOADING;
EkPageState get pageStatus => _pageStatus;
set pageStatus(EkPageState state) {
if (state != null && state != _pageStatus) _pageStatus = state;
notifyListeners();
}
void setPageStatusOnly(EkPageState state){
if (state != null && state != _pageStatus) _pageStatus = state;
}
void notifyLoading() {
if (_pageStatus != EkPageState.LOADING) _pageStatus = EkPageState.LOADING;
notifyListeners();
}
void notifySuccess() {
if (_pageStatus != EkPageState.SUCCESS) _pageStatus = EkPageState.SUCCESS;
notifyListeners();
}
void notifyFailed() {
if (_pageStatus != EkPageState.FAILED) _pageStatus = EkPageState.FAILED;
notifyListeners();
}
void notifyNetFailed() {
if (_pageStatus != EkPageState.NET_FAILED) _pageStatus = EkPageState.NET_FAILED;
notifyListeners();
}
void notifyNoData() {
if (_pageStatus != EkPageState.NO_DATA) _pageStatus = EkPageState.NO_DATA;
notifyListeners();
}
}
供给了各种状况的 notify,运用方按需调用即可。
GetX:
GetX , 现在是热度最高的一个页面状况办理结构,Getx供给了三大部分的才能:路由办理,一系列的工具办法,以及状况办理。
getX问题:
-
GetX 涉及 路由办理、一些工具办法,实际上是一个大杂烩的组件,缺失单一性规划原则
-
路由办理运用杂乱度很高,项目运用、改造本钱很大,收益反而很小。
getX长处:
-
一个简略的呼应式状况办理处理方案。
-
运用相对简略
-
调用无需context,假如需求,能够全局调用
getX 看法
-
getX 是一个大杂烩的组件,好在全体代码不多,而且单独个功用之间耦合度低,能够按需分开运用。
-
虽然 GetX 有缺陷,可是亮点(呼应式状况处理) 也是很有吸引力的,彻底能够去其糙泊取其精华,即运用 GetX 状况办理的呼应式才能。
-
单纯状况办理场景,代码搬迁本钱不高。
GetX 呼应式运用示例:
- 声明一个呼应式变量
//1. .obs 扩展
var countObs = 0.obs; === RxInt(0) // ''.obs [].obs {}.obs user.obs
//2. Rx类
var count = RxInt(0); // RxSting, RxBool,RxNum, RxMap, RxList
//3. Rx泛型
var count = Rx<Int>(0);
var userObs = Rx(user)
- 绑定widget
Obx(() => Text('${countObs.value}'))
- 更新变量的值,自动更新对应的widget
countObs.value += 1;
字节内状况办理场景实践:
(from Flutter中台,2021年数据)
项目 | 项目类型 | flutter规模(按Dart代码行大略归类) | 选择 | 补白(2021.1.21) |
---|---|---|---|---|
西红柿畅听 | 混合 | 小 | setState | 单一页面,业务探究 |
特效君 | 纯flutter | 中小 | provider | |
火山 | 混合app | 中 | provider + flutter_redux | |
西瓜 | 混合 | 大 | provider +bloc +mobx | |
小荷 | 纯flutter | 大 | bloc | 现在往getX 转 |
美好里 | 纯flutter | 大 | getX |
主流状况结构比较:
官方结构比较:setState、Provider、Stream 比较
setState | Provider | Stream | |
---|---|---|---|
长处 | 1.官方供给 2.需求承继StateFullWidget 3.运用简略 |
1.官方供给 2.依靠InheritedWidget 3.运用简略 |
1.官方供给 2.结合StreamBuilder 运用 |
缺乏 | 1.不能准确改写 | 1.需求context 2.存在效果域 3.不能呼应式编程 |
1.功用单一,并不能合适更多杂乱场景 |
其他呼应式结构:
Stream | RxDart | flutter-redux | mobx | bloc | GetX | |
---|---|---|---|---|---|---|
长处 | 1.官方供给 2.结合StreamBuilder 运用 |
1.官方供给 2.实际上是运用了Stream才能(承继) 3.供给了丰富的才能 |
1.流程明晰,套路通用 2.作为状况办理结构,功用齐备性好。 |
1.概念少,易于理解。 2.编码几乎是最简便的,经过注解和codegen减少了样板代码 |
1.运用了RxDart才能 2.业务逻辑和UI别离较好 |
1.将依靠办理结合,无context 2.呼应编程上手简略,功率较高,供给了许多高档的封装,如futurize(一行代码完成Loading, Success, Error状况处理) |
缺乏 | 1.功用单一,并不能合适更多杂乱场景 | 1.运用办法杂乱,大范围运用本钱高 2.UI页面开发办法单一 |
1.redux的编码繁琐 2.样板代码较多 3.运用本钱高 |
1.运用本钱高 2.注解编译生成dart代码,调试本钱高 |
1.运用办法杂乱,大范围运用本钱高 2. UI页面开发办法单一 |
1.不行纯洁, 归于混合插件,供给状况办理、路由办理、依靠办理 2. 路由办理过于杂乱、改动过大、项目契合度不符 |
初步结论:
-
简略页面改写 setState,如splash 页面
-
较页面运用 Provider,办理数据量少
-
假如更杂乱页面,如页面多处改写,页面联动改写 运用 GetX,而 GetX 建议运用 Rx 才能 呼应式改写。
根据 GetX 呼应式改写才能,页面能够能够选用MVRX 规划。
根据GetX 页面状况办理的 MvRx:
MvRx: ModelView ReactiveX
下面是 MVVM 流程图:
View: 在Flutter 中归于 各种 widget,承继 GetX 的 GetView 。
ViewModel: 在Flutter 中 根据 GetX, 运用 GetxController 子类替代,需求呼应ui的数据运用 Rx 对应类型,以支撑呼应编程,改写ui。
Model: 在Flutter中承当非页面状况的数据办理,负责逻辑处理。
而 MvRx:ViewModel 将被 GetX 呼应式的 Rx 模块替代。
Flutter MvRx 元素:
四个中心概念对应Flutter 的方案:
-
State:
-
ViewModel:
-
View:
-
Async:
长处:
-
呼应式更新编程
-
简化页面开发逻辑,页面展现、操控逻辑解耦
-
运用本钱低
缺陷:
-
GetX 组件功用杂乱,需求抽丝剥茧,选择有用的模块
-
需求一定的理解本钱
一个简略的代码示例
下面是一个设置页面是否展现实验室入口的MVVM代码示例:
View 层示例:
class SettingsView extends GetView<SettingsController> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
SizedBox(
height: 20,
),
_buildLogin("登录装备"),
SizedBox(
height: 20,
),
_buildDevice("设备装备"),
SizedBox(
height: 20,
),
Obx(() => controller.isShowLab.value ? _buildLogin("实验室装备") : SizedBox())
],
),
);
}
}
运用 GetX Obx 意味着,对应的(){} 支撑呼应式改变。
ViewModel 层示例:
class SettingsController extends GetxController {
final SettingsModel _settingsModel = SettingsModel();
final RxBool isShowLab = false.obs;
@override
void onInit() {
super.onInit();
_settingsModel.requestUrl("xxxxxx").then(config){
if(config == null) return;
SettingsConfig settingsConfigRes = config;
isShowLab.value = settingsConfigRes.labConfig;
};
}
}
这儿的 bool 类型的 isShowLab,表示是否展现实验室入口,运用 RxBool 意味着 isShowLab 数据改变,对应的 View层对应需求该数据的 widget 会进行对应的改写。
Model 层示例:
class SettingsModel extends StateModel {
SettingsConfig _settingsConfig;
SettingsConfig requestUrl(String url,{Map params}) async{
_settingsConfig = await SettingsApi.requestSettings(SettingsConfigRes()..url=url..params=params);
return _settingsConfig;
}
}
SettingsModel 是用于网络等数据获取。