状况办理

官方介绍地址: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:

  1. 需求结合 StatefullWidget 运用

  2. 短时状况经常被用于一个单独 widget 的本地状况,一般运用 State 和 setState() 来完成。

setState 问题:

  1. 改写是大范围的全体改写,而不是精密的改写

  2. 只有 StatefullWidget 即有状况的 widget,才支撑 setState 办法进行改写

setState 长处:

  1. 官方供给,运用极简略

  2. 调用无需context

provider:

工程结构:

Flutter 页面状态管理 & MvRx方案设计

Flutter 官方引荐,provider 十分好理解而且不需求写许多代码。

底层是运用了InheritedWidget, InheritedNotifier, InheritedModel等才能。InheritedWidget是Flutter中特别重要的一个Widget,它供给了一种数据在widget树中从上到下传递、同享的办法。

provider 中心类

  • ChangeNotifier

  • ChangeNotifierProvider

  • Consumer

ChangeNotifier:

它用于向监听器发送告诉。换言之,假如被定义为 ChangeNotifier,你能够订阅它的状况改变。

Flutter 页面状态管理 & MvRx方案设计

ChangeNotifierProvider:

ChangeNotifierProvider widget 能够向其子孙节点暴露一个 ChangeNotifier 实例。

Flutter 页面状态管理 & MvRx方案设计

Flutter 页面状态管理 & MvRx方案设计

Consumer:

当承继于ChangeNotifier 子类 经过 ChangeNotifierProvider 在运用中与 widget 相关联,则能够运用 Consumer widget。

Flutter 页面状态管理 & MvRx方案设计

有必要指定要拜访的模型类型。在这个示例中,要拜访 CartModel 那么就写上 Consumer<CartModel>。

Consumer widget 仅有有必要的参数就是 builder。当 ChangeNotifier 发生改变的时分会调用 builder 这个函数。(换言之,当调用 notifyListeners() 时,所有相关的 Consumer widget 的 builder 办法都会被调用。)

Provider.of

运用 Provider.of,而且将 listen 设置为 false。用于拜访数据,但又不希望改变ui的场景。

provider 问题:

  1. 需求context 强绑定

  2. 不能改写屏幕外页面

  3. 不支撑呼应式变成

provider 长处:

  1. 运用简略,大约2天内掌握运用。

  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供给了三大部分的才能:路由办理,一系列的工具办法,以及状况办理。

Flutter 页面状态管理 & MvRx方案设计

getX问题:

  1. GetX 涉及 路由办理、一些工具办法,实际上是一个大杂烩的组件,缺失单一性规划原则

  2. 路由办理运用杂乱度很高,项目运用、改造本钱很大,收益反而很小。

getX长处:

  1. 一个简略的呼应式状况办理处理方案。

  2. 运用相对简略

  3. 调用无需context,假如需求,能够全局调用

getX 看法

  1. getX 是一个大杂烩的组件,好在全体代码不多,而且单独个功用之间耦合度低,能够按需分开运用。

  2. 虽然 GetX 有缺陷,可是亮点(呼应式状况处理) 也是很有吸引力的,彻底能够去其糙泊取其精华,即运用 GetX 状况办理的呼应式才能。

  3. 单纯状况办理场景,代码搬迁本钱不高。

GetX 呼应式运用示例:

  1. 声明一个呼应式变量
//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)
  1. 绑定widget
Obx(() => Text('${countObs.value}'))
  1. 更新变量的值,自动更新对应的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. 路由办理过于杂乱、改动过大、项目契合度不符

初步结论:

  1. 简略页面改写 setState,如splash 页面

  2. 较页面运用 Provider,办理数据量少

  3. 假如更杂乱页面,如页面多处改写,页面联动改写 运用 GetX,而 GetX 建议运用 Rx 才能 呼应式改写。

根据 GetX 呼应式改写才能,页面能够能够选用MVRX 规划。

根据GetX 页面状况办理的 MvRx:

MvRx: ModelView ReactiveX

下面是 MVVM 流程图:

Flutter 页面状态管理 & MvRx方案设计

View: 在Flutter 中归于 各种 widget,承继 GetX 的 GetView 。

ViewModel: 在Flutter 中 根据 GetX, 运用 GetxController 子类替代,需求呼应ui的数据运用 Rx 对应类型,以支撑呼应编程,改写ui。

Model: 在Flutter中承当非页面状况的数据办理,负责逻辑处理。

而 MvRx:ViewModel 将被 GetX 呼应式的 Rx 模块替代。

Flutter MvRx 元素:

Flutter 页面状态管理 & MvRx方案设计

四个中心概念对应Flutter 的方案:

  • State:

  • ViewModel:

  • View:

  • Async:

长处:

  1. 呼应式更新编程

  2. 简化页面开发逻辑,页面展现、操控逻辑解耦

  3. 运用本钱低

缺陷:

  1. GetX 组件功用杂乱,需求抽丝剥茧,选择有用的模块

  2. 需求一定的理解本钱

一个简略的代码示例

下面是一个设置页面是否展现实验室入口的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 是用于网络等数据获取。