目录介绍

  • 01.什么是状态管理
  • 02.状态管理方案分类
  • 03.状态管理使用场景
  • 04.Widget管理自己的状态
  • 05.Widget管理子Widget状态
  • 06.简单混合管理状态
  • 07.全局状态如何管理
  • 08.Provider使用方法
  • 09.订阅监听修改状态

推荐

01.什么是状态管理

  • 响应式的编程框架中都会有一个永恒的主题——“状态(State)管理”

    • 在Flutter中,想一个问题,StatefulWidget的状态应该被谁管理?
    • Widget本身?父Widget?都会?还是另一个对象?答案是取决于实际情况!
  • 以下是管理状态的最常见的方法:
    • Widget管理自己的状态。
    • Widget管理子Widget状态。
    • 混合管理(父Widget和子Widget都管理状态)。
    • 不同模块的状态管理。
  • 如何决定使用哪种管理方法?下面给出的一些原则可以帮助你做决定:
    • 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父Widget管理。
    • 如果状态是有关界面外观效果的,例如颜色、动画,那么状态最好由Widget本身来管理。
    • 如果某一个状态是不同Widget共享的则最好由它们共同的父Widget管理。
    • 如果是多个模块需要公用一个状态,那么该怎么处理呢,那可以用Provider。
    • 如果修改了某一个属性,需要刷新多个地方数据。比如修改用户城市id数据,那么则刷新首页n处的接口数据,这个时候可以用订阅监听修改状态

02.状态管理方案分类

  • setState状态管理

    • 优点:

      • 简单场景下特别适用,逻辑简单,易懂易实现
      • 所见即所得,效率比较高
    • 缺点
      • 逻辑与视图耦合严重,复杂逻辑下可维护性很差
      • 数据传输基于依赖传递,层级较深情况下不易维护,可读性差
  • InheritedWidget状态管理
    • 优点

      • 方便数据传输,可以基于InheritedWidget达到逻辑和视图解耦的效果
      • flutter内嵌类,基础且稳定,无代码侵入
    • 缺点
      • 属于比较基础的类,友好性不如封装的第三方库
      • 对于性能需要额外注意,刷新范围如果过大会影响性能
  • Provider状态管理
    • 优点

      • 功能完善,涵盖了ScopedModel和InheritedWidget的所有功能
      • 数据逻辑完美融入了widget树中,代码结构清晰,可以管理局部状态和全局状态
      • 解决了多model和资源回收的问题
      • 对不同场景下使用的provider做了优化和区分
      • 支持异步状态管理和provider依赖注入
    • 缺点
      • 使用不当可能会造成性能问题(大context引起的rebuild)
      • 局部状态之前的数据同步不支持
  • 订阅监听修改状态
    • 有两种:一种是bus事件通知(是一种订阅+观察),另一个是接口注册回调。
    • 接口回调:由于使用了回调函数原理,因此数据传递实时性非常高,相当于直接调用,一般用在功能模块上。
    • bus事件:组件之间的交互,很大程度上降低了它们之间的耦合,使得代码更加简洁,耦合性更低,提升我们的代码质量。

03.状态管理使用场景

  • setState状态管理

    • 适合Widget管理自己的状态,这种很常见,调用setState刷新自己widget改变状态。
    • 适合Widget管理子Widget状态,这种也比较常见。不过这种关联性比较强。

04.Widget管理自己的状态

  • _TapboxAState 类:

    • 管理TapboxA的状态。
    • 定义_active:确定盒子的当前颜色的布尔值。
    • 定义_handleTap()函数,该函数在点击该盒子时更新_active,并调用setState()更新UI。
    • 实现widget的所有交互式行为。
  • 代码如下
    // TapboxA 管理自身状态.
    
    //------------------------- TapboxA ----------------------------------
    
    class TapboxA extends StatefulWidget {
    TapboxA({Key key}) : super(key: key); @override
    _TapboxAState createState() => new _TapboxAState();
    } class _TapboxAState extends State<TapboxA> {
    bool _active = false; void _handleTap() {
    setState(() {
    _active = !_active;
    });
    } Widget build(BuildContext context) {
    return new GestureDetector(
    onTap: _handleTap,
    child: new Container(
    child: new Center(
    child: new Text(
    _active ? 'Active' : 'Inactive',
    style: new TextStyle(fontSize: 32.0, color: Colors.white),
    ),
    ),
    width: 200.0,
    height: 200.0,
    decoration: new BoxDecoration(
    color: _active ? Colors.lightGreen[700] : Colors.grey[600],
    ),
    ),
    );
    }
    }

05.Widget管理子Widget状态

  • 先看一下下面这些是什么

    typedef ValueChanged<T> = void Function(T value);
  • 对于父Widget来说,管理状态并告诉其子Widget何时更新通常是比较好的方式。
    • 例如,IconButton是一个图标按钮,但它是一个无状态的Widget,因为我们认为父Widget需要知道该按钮是否被点击来采取相应的处理。
    • 在以下示例中,TapboxB通过回调将其状态导出到其父组件,状态由父组件管理,因此它的父组件为StatefulWidget
  • ParentWidgetState 类:
    • 为TapboxB 管理_active状态。
    • 实现_handleTapboxChanged(),当盒子被点击时调用的方法。
    • 当状态改变时,调用setState()更新UI。
  • TapboxB 类:
    • 继承StatelessWidget类,因为所有状态都由其父组件处理。
    • 当检测到点击时,它会通知父组件。
    // ParentWidget 为 TapboxB 管理状态.
    
    class ParentWidget extends StatefulWidget {
    @override
    _ParentWidgetState createState() => new _ParentWidgetState();
    } class _ParentWidgetState extends State<ParentWidget> {
    bool _active = false; void _handleTapboxChanged(bool newValue) {
    setState(() {
    _active = newValue;
    });
    } @override
    Widget build(BuildContext context) {
    return new Scaffold(
    appBar: new AppBar(
    title: new Text("Widget管理子Widget状态"),
    ),
    body: new ListView(
    children: [
    new Text("Widget管理子Widget状态"),
    new TapboxB(
    active: _active,
    onChanged: _handleTapboxChanged,
    ),
    ],
    ),
    );
    }
    } //------------------------- TapboxB ---------------------------------- class TapboxB extends StatefulWidget{ final bool active;
    final ValueChanged<bool> onChanged; TapboxB({Key key , this.active : false ,@required this.onChanged }); @override
    State<StatefulWidget> createState() {
    return new TabboxBState();
    } } class TabboxBState extends State<TapboxB>{ void _handleTap() {
    widget.onChanged(!widget.active);
    } @override
    Widget build(BuildContext context) {
    return new GestureDetector(
    onTap: _handleTap,
    child: new Container(
    child: new Center(
    child: new Text(
    widget.active ? 'Active' : 'Inactive',
    ),
    ),
    width: 100,
    height: 100,
    decoration: new BoxDecoration(
    color: widget.active ? Colors.lightGreen[700] : Colors.grey[850],
    ),
    ),
    );
    }
    }

06.简单混合管理状态

  • 对于一些组件来说,混合管理的方式会非常有用。

    • 在这种情况下,组件自身管理一些内部状态,而父组件管理一些其他外部状态。
  • 在下面TapboxC示例中
    • 手指按下时,盒子的周围会出现一个深绿色的边框,抬起时,边框消失。点击完成后,盒子的颜色改变。
    • TapboxC将其_active状态导出到其父组件中,但在内部管理其_highlight状态。
    • 这个例子有两个状态对象_ParentWidgetState_TapboxCState
  • _ParentWidgetStateC类:
    • 管理_active 状态。
    • 实现 _handleTapboxChanged() ,当盒子被点击时调用。
    • 当点击盒子并且_active状态改变时调用setState()更新UI。
  • _TapboxCState 对象:
    • 管理_highlight 状态。
    • GestureDetector监听所有tap事件。当用户点下时,它添加高亮(深绿色边框);当用户释放时,会移除高亮。
    • 当按下、抬起、或者取消点击时更新_highlight状态,调用setState()更新UI。
    • 当点击时,将状态的改变传递给父组件。
    //---------------------------- ParentWidget ----------------------------
    
    class ParentWidgetC extends StatefulWidget {
    @override
    _ParentWidgetCState createState() => new _ParentWidgetCState();
    } class _ParentWidgetCState extends State<ParentWidgetC> {
    bool _active = false; void _handleTapboxChanged(bool newValue) {
    setState(() {
    _active = newValue;
    });
    } @override
    Widget build(BuildContext context) {
    return new Scaffold(
    appBar: new AppBar(
    title: new Text("简单混合管理状态"),
    ),
    body: new Container(
    child: new ListView(
    children: [
    new Text("_ParentWidgetCState状态管理"),
    new Padding(padding: EdgeInsets.all(10)),
    new Text(
    _active ? 'Active' : 'Inactive',
    ),
    new Padding(padding: EdgeInsets.all(10)),
    new Text("_TapboxCState状态管理"),
    new TapboxC(
    active: _active,
    onChanged: _handleTapboxChanged,
    )
    ],
    ),
    ),
    );
    }
    } //----------------------------- TapboxC ------------------------------ class TapboxC extends StatefulWidget {
    TapboxC({Key key, this.active: false, @required this.onChanged})
    : super(key: key); final bool active;
    final ValueChanged<bool> onChanged; @override
    _TapboxCState createState() => new _TapboxCState();
    } class _TapboxCState extends State<TapboxC> {
    bool _highlight = false; void _handleTapDown(TapDownDetails details) {
    setState(() {
    _highlight = true;
    });
    } void _handleTapUp(TapUpDetails details) {
    setState(() {
    _highlight = false;
    });
    } void _handleTapCancel() {
    setState(() {
    _highlight = false;
    });
    } void _handleTap() {
    widget.onChanged(!widget.active);
    } @override
    Widget build(BuildContext context) {
    // 在按下时添加绿色边框,当抬起时,取消高亮
    return new GestureDetector(
    onTapDown: _handleTapDown, // 处理按下事件
    onTapUp: _handleTapUp, // 处理抬起事件
    onTap: _handleTap,
    onTapCancel: _handleTapCancel,
    child: new Container(
    child: new Center(
    child: new Text(widget.active ? 'Active' : 'Inactive',
    style: new TextStyle(fontSize: 32.0, color: Colors.white)),
    ),
    width: 200.0,
    height: 200.0,
    decoration: new BoxDecoration(
    color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
    border: _highlight
    ? new Border.all(
    color: Colors.teal[700],
    width: 10.0,
    )
    : null,
    ),
    ),
    );
    }
    }

07.全局状态如何管理

  • 当应用中需要一些跨组件(包括跨路由)的状态需要同步时,上面介绍的方法便很难胜任了。

    • 比如,我们有一个设置页,里面可以设置应用的语言,我们为了让设置实时生效,我们期望在语言状态发生改变时,APP中依赖应用语言的组件能够重新build一下,但这些依赖应用语言的组件和设置页并不在一起,所以这种情况用上面的方法很难管理。
    • 这时,正确的做法是通过一个全局状态管理器来处理这种相距较远的组件之间的通信。
  • 目前主要有两种办法:
    • 1.实现一个全局的事件总线,将语言状态改变对应为一个事件,然后在APP中依赖应用语言的组件的initState 方法中订阅语言改变的事件。当用户在设置页切换语言后,我们发布语言改变事件,而订阅了此事件的组件就会收到通知,收到通知后调用setState(...)方法重新build一下自身即可。
    • 2.使用一些专门用于状态管理的包,如Provider、Redux,读者可以在pub上查看其详细信息。
  • 举一个简答的案例来实践
    • 本实例中,使用Provider包来实现跨组件状态共享,因此我们需要定义相关的Provider。
    • 需要共享的状态有登录用户信息、APP主题信息、APP语言信息。由于这些信息改变后都要立即通知其它依赖的该信息的Widget更新,所以我们应该使用ChangeNotifierProvider,另外,这些信息改变后都是需要更新Profile信息并进行持久化的。
    • 综上所述,我们可以定义一个ProfileChangeNotifier基类,然后让需要共享的Model继承自该类即可,ProfileChangeNotifier定义如下:
      class ProfileChangeNotifier extends ChangeNotifier {
      Profile get _profile => Global.profile; @override
      void notifyListeners() {
      Global.saveProfile(); //保存Profile变更
      super.notifyListeners(); //通知依赖的Widget更新
      }
      }
    • 用户状态
      • 用户状态在登录状态发生变化时更新、通知其依赖项,我们定义如下:
      class UserModel extends ProfileChangeNotifier {
      User get user => _profile.user; // APP是否登录(如果有用户信息,则证明登录过)
      bool get isLogin => user != null; //用户信息发生变化,更新用户信息并通知依赖它的子孙Widgets更新
      set user(User user) {
      if (user?.login != _profile.user?.login) {
      _profile.lastLogin = _profile.user?.login;
      _profile.user = user;
      notifyListeners();
      }
      }
      }

08.Provider使用方法

8.1 正确地初始化 Provider

  • 如下所示,create是必须要传递的参数

    ChangeNotifierProvider(
    create: (_) => MyModel(),
    child: ...
    )
  • 实际开发中如何应用
    builder: (BuildContext context, Widget child) {
    return MultiProvider(providers: [
    ChangeNotifierProvider(create: (context) => BusinessPattern()),
    ]);
    },
  • 然后看一下BusinessPattern是什么?
    class BusinessPattern extends ChangeNotifier {
    PatternState currentState = PatternState.none;
    void updateBusinessPatternState(PatternState state) {
    if (currentState.index != state.index) {
    LogUtils.d('当前模式:$currentState');
    LogUtils.d('更新模式:$state');
    currentState = state;
    notifyListeners();
    }
    }
    }

8.2 如何获取Provider取值

  • 一种是 Provider.of(context) 比如:

    Widget build(BuildContext context) {
    final text = Provider.of<String>(context);
    return Container(child: Text(text));
    }
    • 遇到的问题:由于 Provider 会监听 Value 的变化而更新整个 context 上下文,因此如果 build 方法返回的 Widget 过大过于复杂的话,刷新的成本是非常高的。那么我们该如何进一步控制 Widget 的更新范围呢?
    • 解决办法:一个办法是将真正需要更新的 Widget 封装成一个独立的 Widget,将取值方法放到该 Widget 内部。
    Widget build(BuildContext context) {
    return Container(child: MyText());
    } class MyText extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    final text = Provider.of<String>(context);
    return Text(text);
    }
    }
  • Consumer 是 Provider 的另一种取值方式
    • Consumer 可以直接拿到 context 连带 Value 一并传作为参数传递给 builder ,使用无疑更加方便和直观,大大降低了开发人员对于控制刷新范围的工作成本。
    Widget getWidget2(BuildContext context) {
    return Consumer<BusinessPattern>(builder: (context, businessModel, child) {
    switch (businessModel.currentState) {
    case PatternState.none:
    return Text("无模式");
    break;
    case PatternState.normal:
    return Text("正常模式");
    break;
    case PatternState.small:
    return Text("小屏模式");
    break;
    case PatternState.overview:
    return Text("全屏模式");
    break;
    default:
    return Text("其他模式");
    return SizedBox();
    }
    });
    }
  • Selector 是 Provider 的另一种取值方式
    • Selector 是 3.1 推出的功能,目的是更近一步的控制 Widget 的更新范围,将监听刷新的范围控制到最小
    • selector:是一个 Function,传入 Value ,要求我们返回 Value 中具体使用到的属性。
    • shouldRebuild:这个 Function 会传入两个值,其中一个为之前保持的旧值,以及此次由 selector 返回的新值,我们就是通过这个参数控制是否需要刷新 builder 内的 Widget。如果不实现 shouldRebuild ,默认会对 pre 和 next 进行深比较(deeply compares)。如果不相同,则返回 true。
    • builder:返回 Widget 的地方,第二个参数 定义的参数,就是我们刚才 selector 中返回的 参数。
    Widget getWidget4(BuildContext context) {
    return Selector<BusinessPattern, PatternState>(
    selector: (context, businessPattern) =>
    businessPattern.currentState,
    builder: (context, state, child) {
    switch (state) {
    case PatternState.none:
    return Text("无模式");
    break;
    case PatternState.normal:
    return Text("正常模式");
    break;
    case PatternState.small:
    return Text("小屏模式");
    break;
    case PatternState.overview:
    return Text("全屏模式");
    break;
    default:
    return Text("其他模式");
    return SizedBox();
    }
    }
    );

8.3 修改Provider状态

  • 如何调用修改状态管理

    BusinessPatternService _patternService = serviceLocator<BusinessPatternService>();
    //修改状态
    _patternService.nonePattern();
    _patternService.normalPattern();
  • 然后看一下normalPattern的具体实现代码
    class BusinessPatternServiceImpl extends BusinessPatternService {
    
      final BuildContext context;
    BusinessPatternServiceImpl(this.context); PatternState get currentPatternState =>
    _getBusinessPatternState(context).currentState; BusinessPattern _getBusinessPatternState(BuildContext context) {
    return Provider.of<BusinessPattern>(context, listen: false);
    } @override
    void nonePattern() {
    BusinessPattern _patternState = _getBusinessPatternState(context);
    _patternState.updateBusinessPatternState(PatternState.none);
    } @override
    void normalPattern() {
    BusinessPattern _patternState = _getBusinessPatternState(context);
    _patternState.updateBusinessPatternState(PatternState.normal);
    }
    }

8.4 关于Provider刷新

  • 状态发生变化后,widget只会重新build,而不会重新创建(重用机制跟key有关,如果key发生变化widget就会重新生成)

09.订阅监听修改状态

  • 首先定义抽象类。还需要写上具体的实现类

    typedef LocationDataChangedFunction = void Function(double);
    
    abstract class LocationListener {
    /// 注册数据变化的回调
    void registerDataChangedFunction(LocationDataChangedFunction function);
    /// 移除数据变化的回调
    void unregisterDataChangedFunction(LocationDataChangedFunction function);
    /// 更新数据的变化
    void locationDataChangedCallback(double angle);
    } class LocationServiceCenterImpl extends LocationListener { List<LocationDataChangedFunction> _locationDataChangedFunction = List(); @override
    void locationDataChangedCallback(double angle) {
    _locationDataChangedFunction.forEach((function) {
    function.call(angle);
    });
    } @override
    void registerDataChangedFunction(LocationDataChangedFunction function) {
    _locationDataChangedFunction.add(function);
    } @override
    void unregisterDataChangedFunction(LocationDataChangedFunction function) {
    _locationDataChangedFunction.remove(function);
    }
    }
  • 那么如何使用呢?在需要用的页面添加接口回调监听
    _locationListener.registerDataChangedFunction(_onDataChange);
    void _onDataChange(double p1) {
    //监听回调处理
    }
  • 那么如何发送事件,这个时候
    LocationListener _locationListener = locationService();
    _locationListener.locationDataChangedCallback(520.0);

fluter Utils 工具类库:https://github.com/yangchong211/YCFlutterUtils

flutter 混合项目代码案例:https://github.com/yangchong211/YCHybridFlutter

Flutter如何状态管理的更多相关文章

  1. (转)flutter 新状态管理方案 Provide (一)-使用

    flutter 新状态管理方案 Provide (一)-使用     版权声明:本文为博主原创文章,基于CC4.0协议,首发于https://kikt.top ,同步发于csdn,转载必须注明出处! ...

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

    为了弄懂Flutter的状态管理, 我用10种方法改造了counter app 本文通过改造flutter的counter app, 展示不同的状态管理方法的用法. 可以直接去demo地址看代码: h ...

  3. Flutter Bloc状态管理 简单上手

    我们都知道,Flutter中Widget的状态控制了UI的更新,比如最常见的StatefulWidget,通过调用setState({})方法来刷新控件.那么其他类型的控件,比如StatelessWi ...

  4. Flutter 对状态管理的认知与思考

    前言 由 编程技术交流圣地[-Flutter群-] 发起的 状态管理研究小组,将就 状态管理 相关话题进行为期 两个月 的讨论. 目前只有内定的 5 个人参与讨论,如果你对 状态管理 有什么独特的见解 ...

  5. Flutter 状态管理 flutter_Provide

    项目的商品类别页面将大量的出现类和类中间的状态变化,这就需要状态管理.现在Flutter的状态管理方案很多,redux.bloc.state.Provide. Scoped Model : 最早的状态 ...

  6. flutter Provide 状态管理篇

    Provide是Google官方推出的状态管理模式.官方地址为: https://github.com/google/flutter-provide 现在Flutter的状态管理方案很多,redux. ...

  7. Flutter实战视频-移动电商-24.Provide状态管理基础

    24.Provide状态管理基础 Flutter | 状态管理特别篇 —— Provide:https://juejin.im/post/5c6d4b52f265da2dc675b407?tdsour ...

  8. Flutter状态管理之provide和provider的使用区别

    说道状态管理不得不说谷歌的亲自开发的两款状态管理Widget:第一个是provide,第二个是provider. 这两个的区别就是一个出来的早,现在好像没整么更新了.第二个是2019才出来的目前的版本 ...

  9. Flutter状态管理Provider,简单上手

    在之前的文章中介绍了 Google 官方仓库下的一个状态管理 Provide.乍一看这俩玩意可能很容易就被认为是同一个东西,仔细一看,这不就差了一个字吗,有什么区别呢. 首先,你要知道的最大的一个区别 ...

  10. Flutter移动电商实战 --(24)Provide状态管理基础

    Flutter | 状态管理特别篇 —— Provide:https://juejin.im/post/5c6d4b52f265da2dc675b407?tdsourcetag=s_pcqq_aiom ...

随机推荐

  1. Delphi 异常处理 详解

    [1] Exception类的定义在SysUtils单元中. [2] Delphi也支持不从Exception继承的异常类,但是我觉得这么做并不十分的明智. 一.异常的源 在Delphi的应用程序中, ...

  2. Guava EventBus的具体使用以及源码解析

    使用Guava EventBus对系统进行异步解耦改造 一.背景 最近在写的项目里,在使用定时器进行自动任务下派时,发现之前写的程序中将包括启动流程.地图更新.发送短信.效能计算等操作全部集成在同一个 ...

  3. UVA1108 Mining Your Own Business 题解

    题目传送门 题意 在一个无向图上选择尽量少的点涂黑,使得删除任意一个点后,每个连通分量里都至少有一个黑点(多组数据). 正文 观察题意,发现这是个 Tarjan 求点双连通分量的板子. 考虑在求点双连 ...

  4. idea 报错: Unable to import maven project: See logs for details

    错误再现: idea 工具日志: 1) No implementation for org.apache.maven.model.path.PathTranslator was bound. whil ...

  5. Java并发编程实例--18.修改锁的公平性

    ReentrantLock和ReentrantReadWriteLock类的构造函数可接受一个布尔类型参数fair,表示你可以控制这2个类的行为. 其默认值为false,代表non-fair(不公平) ...

  6. BUUCTF [强网杯 2019]随便注 1

    1. 拿到题目,先输入一个1'试一下是否存在注入点 报错 error 1064 : You have an error in your SQL syntax; check the manual tha ...

  7. nginx中使用perl脚本来定制一些请求转发等等

    http://t.zoukankan.com/carriezhangyan-p-9359708.html https://blog.csdn.net/weixin_28917223/article/d ...

  8. linux下MariaDB安装

    一条命令安装Mariadb 首先在/etc/yum.repos.d下创建一个MariaDB.repo文件 vim /etc/yum.repos.d/MariaDB.repo 添加以下配置 [maria ...

  9. 05、secs协议常见问题分析以及如何建立通信

    1.建立通信 在主机和设备之间发送SECS-II消息之前,必须首先"建立"通信.这是通过S1F13(建立通信请求)消息来完成的.这应该是在初始启动后或在长时间不通信之后发送的第一个 ...

  10. 从零开始学Spring Boot系列-返回json数据

    欢迎来到从零开始学Spring Boot的旅程!在Spring Boot中,返回JSON数据是很常见的需求,特别是当我们构建RESTful API时.我们对上一篇的Hello World进行简单的修改 ...