Flutter - 状态管理

Flutter作为响应式开发的框架,状态管理是Flutter中非常重要的一个部分,接下来就来看看Flutter中都有哪些状态管理的方式。


State/InheritedWidget

StateInheritedWidgetFlutter状态管理中扮演着重要的角色,不少的系统控件都采用了这种方式进行状态管理。


State

因为Widget是不可变的,但是State支持跨帧保存数据,所以Widget可以实现跨帧的状态恢复/刷新。当我们调用setState((){});方法的时候,State内部会通过调用markNeedsLayout方式,将对应的Widget设置为_diry(脏标记),从而在下一帧执行WidgetBinding.darwFrame时调用performLayout进行更新。


InheritedWidget

InheritedWidgetFlutter常用于数据的共享(从上至下),被InheritedWidget包裹起来的child可以通过BuildContext来获取相对应的数据。

所以StateInheritedWidget组成了Flutter中最基础的状态管理模式,通过State保存数据和管理状态,通过InheritedWidget来进行数据的共享,从而实现了跨页面的数据传递。


示例

class InheritedText extends InheritedWidget {
  final String text;

  InheritedText({this.text, Widget child})
      : super(child: child);

  @override
  bool updateShouldNotify(covariant InheritedText oldWidget) {
    return text != oldWidget.text;
  }

  static InheritedText of(BuildContext context) {
    // 此方法已被标记位过期方法,建议使用下面的方法
    // return context.inheritFromWidgetOfExactType(InheritedText);
    return context.dependOnInheritedWidgetOfExactType() ?? null;
  }
}

class DemoPage extends StatefulWidget {
  @override
  _DemoPageState createState() => _DemoPageState();
}

class _DemoPageState extends State {
  String _text = "init";

  @override
  Widget build(BuildContext context) {
    return InheritedText(
      text: _text,
      child: Scaffold(
        appBar: AppBar(),
        body: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Builder(builder: (context) {
              return Text(
                InheritedText.of(context)?.text ?? "null",
                style: TextStyle(color: Colors.blue),
              );
            }),
            NextPage(),
          ],
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              _text = "Hello";
            });
          },
        ),
      ),
    );
  }
}

class NextPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text(InheritedText.of(context)?.text ?? ""));
  }
}


Stream

Stream达标事件流或者管道,通过Stream可以快速的实现给予事件驱动的业务逻辑,界面通过订阅事件,并针对事件进行变换处理(非必须),最后可以实现界面跟着事件流/管道进行更新。如下图:

image-20210324142246528.png


简单Demo

如何通过Stream更新StatelessWidget

class StreamDemoPage extends StatelessWidget {
  final StreamController _controller;

  StreamDemoPage({Key key}): 
    _controller = StreamController(), 
    super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Stream"),),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Text("StatelessWidget 利用 Stream 完成UI刷新"),
          StreamBuilder(
            initialData: 0,
            stream: _controller.stream,
            builder: (context, snapshot) {
              return Center(child: Text("${snapshot.data}"),);
            },
          )
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 导入math package
          // import 'dart:math';
          _controller.add(Random.secure().nextInt(1000));
        },
        child: Icon(Icons.refresh),
      ),
    );
  }
}

效果如下:

stream.gif


Stream的工作流程

Flutter中的StreamStreamControllerStreamSinkStreamSubscription都是对外开放的接口抽象,内部实现都是私有类。那么他们具体是什么关系呢?又是怎么实现事件流的呢?查看下图

stream

Flutter中的事件流用Stream表示,为了能方便控制Stream,官方提供了StreamController作为管理接口;同时StreamController对外提供了StreamSink对象作为事件的输入口,可以通过sink属性访问;又提供Stream对象用于监听和变化事件流;最后通过Stream订阅得到Subscription,可以管理事件订阅。

总结一下就是:

  • StreamController 用于控制Stream的过程,提供各种借口用于创建各种事件流
  • StreamSink 事件的输入口,主要提供了addaddStream等方法
  • Stream 事件源本身,一般用于监听事件流或者对事件流进行变换,如listenwheremap
  • StreamSubscription 事件订阅后的得到的对象,可以用于取消订阅、暂停等操作

作为事件的入口,当通过StreamSink.add添加一个事件是,事件最后会回调到Stream.listen中传入的onData方法,从addonData的这个过程,在Stream的内部就是通过_zone.runUnaryGuarded进行衔接的,而完成这个衔接的恰好就是StreamSubscription

stream

1、Streamlisten时传入了onData方法用于回调,这个回调方法最终会被传入StreamSubscription对象里,之后通过zone.registerUnaryCallback注册得到_onData标识,这个_onData标识属于当前Zone内的全局标识,只要获取_onData就可以通过Zone直接回调数据到listen中的onData

2、StreamSink在添加事件的时候,会执行StreamSubscription中的_sendData方法(会根据同步还是异步分别调用add/addPending),然后通过_zone.runUnaryGuarded(_onData, data)执行上一步得到的_onData对象,触发listen传入的onData方法,返回数据给订阅者。


上面的流程是同步Stream,那么他的异步流程是怎么样实现的呢?

前面的部分都是相同的,只是在_sendData方法中略有不同。同步的调用add,异步的则是调用addPending

stream 异步


Stream 同步、异步

Stream除了异步执行以外,还可以同步执行,通过设置sync字段来控制,内部就会通过同步/还是异步返回不同的具体实例对象。

Stream构造方法
    // 普通构造方法
    factory StreamController(
      {void onListen()?,
      void onPause()?,
      void onResume()?,
      FutureOr onCancel()?,
      bool sync = false}) {
    return sync
        ? _SyncStreamController(onListen, onPause, onResume, onCancel)
        : _AsyncStreamController(onListen, onPause, onResume, onCancel);
  
  /// 广播的stream
  factory StreamController.broadcast(
      {void onListen()?, void onCancel()?, bool sync = false}) {
    return sync
        ? _SyncBroadcastStreamController(onListen, onCancel)
        : _AsyncBroadcastStreamController(onListen, onCancel);
  }

可以看出会根据sync返回不同的实例对象,根据构造方法和sync不同,分别有四个实例对象。

SyncStreamController._sendData
  void _sendData(T data) {
    if (_isEmpty) return;
    if (_hasOneListener) {
      _state |= _BroadcastStreamController._STATE_FIRING;
      _BroadcastSubscription firstSubscription =
          _firstSubscription as dynamic;
      firstSubscription._add(data);
      _state &= ~_BroadcastStreamController._STATE_FIRING;
      if (_isEmpty) {
        _callOnCancel();
      }
      return;
    }
    _forEachListener((_BufferingStreamSubscription subscription) {
      subscription._add(data);
    });
  }
ASyncStreamController._sendData
  void _sendData(T data) {
    for (var subscription = _firstSubscription;
        subscription != null;
        subscription = subscription._next) {
      subscription._addPending(new _DelayedData(data));
    }
  }

通过上面的两个sendData方法的分析,可以看出两者最大的区别就是一个是调用add方法另外一个是addPending方法。

其中addPending方法最终会调用到stream_impl.dart的schedule方法中

  void schedule(_EventDispatch dispatch) {
    if (isScheduled) return;
    assert(!isEmpty);
    if (_eventScheduled) {
      assert(_state == _STATE_CANCELED);
      _state = _STATE_SCHEDULED;
      return;
    }
    scheduleMicrotask(() {
      int oldState = _state;
      _state = _STATE_UNSCHEDULED;
      if (oldState == _STATE_CANCELED) return;
      handleNext(dispatch);
    });
    _state = _STATE_SCHEDULED;
  }

整理一下异步的Stream发送流程,如下图:

image-20210325095747120.png


StreamController的种类

上已经了解到Stream分区异步/同步两种类型,他们最要的区别就是混入的接口不一致,同步的混入的是_SyncStreamControllerDispatch、异步混入的是_AsyncStreamControllerDispatch

  • 同步 _SyncStreamController
  • 异步 _AsyncStreamController
  • 同步广播 _SyncBroadcastStreamController
  • 异步广播 _AsyncBroadcastStreamController
image-20210325101249941.png

Stream 变换

Stream支持事件的变换处理,通过Stream变化可以让事件经过筛选和多次处理,从而达到最终效果。

image-20210325114427202.png

一般操作符变换实现对象,都是继承了_ForwardingStream,在它内部的_ForwardingStreamSubscription中,会把上一个Streamlisten添加到新的_handleData回调,之后再回调里面调用新的(变换之后的)Stream_handleData,通过这样子的嵌套回调,让Stream在多次变换之后一直往后执行。


StreamBuilder

StreamBuilder是基于Stream的封装,能够让开发者快速的根据Stream构建应用。比如上面的基于Stream让StatelessWidget实现刷新效果的Demo。

那么StreamBuilder的内部是怎么实现的呢?下面是关键代码片段

class _StreamBuilderBaseState extends State> {
  StreamSubscription _subscription;
  S _summary;

  /// 进行监听
  @override
  void initState() {
    super.initState();
    _summary = widget.initial();
    _subscribe();
  }

  /// 重新订阅
  @override
  void didUpdateWidget(StreamBuilderBase oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.stream != widget.stream) {
      if (_subscription != null) {
        _unsubscribe();
        _summary = widget.afterDisconnected(_summary);
      }
      _subscribe();
    }
  }

  @override
  Widget build(BuildContext context) => widget.build(context, _summary);

  /// 销毁的是取消订阅
  @override
  void dispose() {
    _unsubscribe();
    super.dispose();
  }

  /// 订阅
  void _subscribe() {
    if (widget.stream != null) {
      _subscription = widget.stream.listen((T data) {
        setState(() {
          _summary = widget.afterData(_summary, data);
        });
      }, onError: (Object error) {
        setState(() {
          _summary = widget.afterError(_summary, error);
        });
      }, onDone: () {
        setState(() {
          _summary = widget.afterDone(_summary);
        });
      });
      _summary = widget.afterConnected(_summary);
    }
  }

  /// 取消订阅
  void _unsubscribe() {
    if (_subscription != null) {
      _subscription.cancel();
      _subscription = null;
    }
  }
}


整理之后的流程图如下:

image-20210325135515916.png


RxDart

其实从订阅和变换的角度就可以看出,Dart中的Stream已经有用了ReactiveX的设计思想,RxDart的出现就是为了能帮助那些了解过ReactiveX的框架的开发者,能够快速的根据之前编写习惯上手,其实RxDart底层也是基于Stream的一种封装。下图就是RxDart和Stream的对应关系

Dart RxDart
StreamController Subject
Stream Observable

下面用一个PublishSubject的发送和监听做示例:


创建Subject
class PublishSubject extends Subject {
  PublishSubject._(StreamController controller, Stream stream)
      : super(controller, stream);

  /// 工厂方法内部,可以很明显的看到这里就是创建了一个广播类型的StreamController
  factory PublishSubject(
      {void Function() onListen, void Function() onCancel, bool sync = false}) {
    // ignore: close_sinks
    final controller = StreamController.broadcast(
      onListen: onListen,
      onCancel: onCancel,
      sync: sync,
    );

    return PublishSubject._(
      controller,
      controller.stream,
    );
  }
}


添加事件

因为PublishSubject继承自Subject,所以add方法在Subject之中:

  @override
  void add(T event) {
    if (_isAddingStreamItems) {
      throw StateError(
          'You cannot add items while items are being added from addStream');
    }

    _add(event);
  }

    /// 可以看到这里就是调用了controller.add方法
  void _add(T event) {
    onAdd(event);

    _controller.add(event);
  }


监听

因为PublishSubject继承自Subject,所以listen方法在Subject之中:


    // 这里也可以发现,onListen也是controller的listen
    @override
  set onListen(void Function() onListenHandler) {
    _controller.onListen = onListenHandler;
  }


销毁

因为PublishSubject继承自Subject,所以close方法在Subject之中:

  @override
  Future close() {
    if (_isAddingStreamItems) {
      throw StateError(
          'You cannot close the subject while items are being added from addStream');
    }

    /// 这里也是调用的controller的close
    return _controller.close();
  }


这里可能就有疑问了,如果只是提供了对Stream的一层封装,那为什么ReactiveX还要如此大费周章呢?那是因为对于Stream的变换提供了很多的方法。包括bufferbufferCountbufferTestbufferTimecontactWithdebouncedebounceTime等等,查看更多请点我


一个监听用户输入的demo
class RxDartDemoPage extends StatefulWidget {
  @override
  _RxDartDemoPageState createState() => _RxDartDemoPageState();
}

class _RxDartDemoPageState extends State {
  PublishSubject _subject;
  TextEditingController _editingController;
  Stream _keywordStream;

  @override
  void initState() {
    super.initState();
    _editingController = TextEditingController();
    _subject = PublishSubject();

    _keywordStream = _subject
        .debounceTime(Duration(milliseconds: 500))
        .map((event) => "搜索关键字: $event");

    _editingController.addListener(() {
      _subject.add(_editingController.text);
    });
  }

  @override
  void dispose() {
    _subject?.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("RxDart")),
      body: Column(
        children: [
          TextField(
            controller: _editingController,
          ),
          SizedBox(height: 40),
          StreamBuilder(
            stream: _keywordStream,
            builder: (context, snapshot) {
              return Center(child: Text(snapshot.data ?? "请输入关键词"));
            },
          )
        ],
      ),
    );
  }
}


BLoC

BLoC全称是Bussiness Logic Component,是谷歌提出的一种设计模式,BLoC利用了Flutter响应式构建的特点,通过流的方式实现界面的异步渲染,开发者可以铜鼓BLoC可以快速实现业务与界面的分离效果。

BLoC主要是通过StreamStreamBuilder结合实现,目的就是把UI和逻辑分离。


demo

比如新建工程中的默认实现(点击加号,数字加一),如果使用BLoC来实现就是新建一个类,内部提供两个对外开放的内容,一个是stream另外一个则是add方法,通过streamStreamBuilder进行关联,当数据发生改变时主动更新UI;通过add方法对外公开,提供给按钮点击调用。

class BLoCDemoPage extends StatefulWidget {
  @override
  _BLoCDemoPageState createState() => _BLoCDemoPageState();
}

class _BLoCDemoPageState extends State {
  final _CountBLoC _bLoC = _CountBLoC();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("BLoC"),
      ),
      body: Column(
        children: [
          Text("BLoC大多是利用Stream和StreamBuilder实现,更多的是一种设计模式的思路,好处就是分离UI和逻辑层"),
          StreamBuilder(
            initialData: 0,
            stream: _bLoC.countStream,
            builder: (context, snapshot) => Center(
              child: Text(
                "${snapshot.data}",
                style: TextStyle(fontSize: 30, color: Colors.redAccent),
              ),
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: _bLoC.add,
      ),
    );
  }

  @override
  void dispose() {
    _bLoC.dispose();
    super.dispose();
  }
}

class _CountBLoC {
  int _count = 0;
  StreamController _streamController = StreamController();
  /// 提供给外界更新使用
  Stream get countStream => _streamController.stream;

  /// 触发更新逻辑
  void add() {
    _count++;
    _streamController.add(_count);
  }

  /// 销毁
  dispose() {
    _streamController?.close();
  }
}


流程图

[图片上传失败...(image-682214-1616915696007)]


scoped_model

scoped_modelFlutter中最简单的第三方状态的管理框架,它巧妙的利用了Flutter中的一些特性,只有一个dart的文件情况下,实现了实用的状态管理模型。使用它一般需要三步。

  • 1、新建一个类并继承Model,并且在想要更新UI的时候调用notifyListenrs
  • 2、使用ScopedModel控件加载Model
  • 3、使用ScopedModelDescendant或者ScopedModel.of(context)加载model内的数据进行显示

demo

import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart' as sc;

class ScopeModelDemoPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Scope model")),
      body: SafeArea(
        child: Container(
          child: sc.ScopedModel<_CountModel>(
            model: _CountModel(),
            child: sc.ScopedModelDescendant<_CountModel>(
              builder: (context, child, model) {
                return Column(
                  children: [
                    Expanded(child: Center(child: Text(model.count.toString()))),
                    Center(child: FlatButton(
                      onPressed: model.add,
                      color: Colors.blue,
                      child: Icon(Icons.add),
                    ),),
                  ],
                );
              },
            )
          ),
        ),
      ),
    );
  }
}

class _CountModel extends sc.Model {
  static _CountModel of(BuildContext context) =>
      sc.ScopedModel.of<_CountModel>(context);

  int _count = 0;

  int get count => _count;

  void add() {
    _count++;
    notifyListeners();
  }
}


流程图

在查看ScopedModel的源码之后,发现他首先使用AnimatedBuilder包装起来,AnimatedBuilder继承了AnimatedWidget,在AnimatedWidget的生命周期中会对Listenable接口添加监听,而Model恰好就实现了Listenable接口,从而可以达到刷新的效果。ScopedModel内部除了使用AnimatedBuilder包装起来之外,还使用_InheritedModel再次进行了包装,保证了数据向下传递和共享。

ScopedModel.build方法源码
@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
    animation: model,
    builder: (context, _) => _InheritedModel(model: model, child: child),
  );
}
image-20210325173621768.png


flutter_reduce

redux是一种单向数据流架构。在Redux中,数据都是存储在单一信源(Store)中,然后数据存储的时候通过Reducer进行更新,而触发更新的动作就是Action。之所以说他是单向数据流,那是因为redux通过action发出的行为,通过reducer更新之后并把数据保存到store中,在加上Widget之后就变成了一个闭环,如下图:

流程图

image-20210326135834721.png


使用流程

  • 1、创建State
  • 2、创建Action
  • 3、创建Reducer
  • 4、创建/保存Store
  • 5、关联Widget
  • 6、发出Action,触发第4步

demo

下面用一个两个页面之前的数据更新demo进行演示:

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';

/// 1、创建State
class ReduxCountState {
  int _count;

  int get count => _count;

  ReduxCountState(this._count);
}

/// 2、创建Action
enum ReduxAction { increment }

/// 3、创建Reducer
ReduxCountState reducer(ReduxCountState state, dynamic action) {
  switch (action) {
    case ReduxAction.increment:
      return ReduxCountState(state.count + 1);
    default:
      return state;
  }
}

/// 拦截器,拦截器介于Action和Reducer之间,如果我们要对一些事件进行拦截,就可以在这里处理
/// 举个例子:当我们更新用户信息的时候(假设有头像,名称),需要去刷新,当我们只更新
/// 名称的时候,由于头像没更新,我不希望头像也倍刷新一次,此时就可以根据action,进行拦截不响应处理
class ReduxCountMiddleware implements MiddlewareClass {
  @override
  call(Store store, action, next) {
    /// 只更新偶数,奇数不处理
    if (store.state.count % 2 != 0) {
      next(action);
      print("xxxxxxxxx 我是拦截器,偶数通过");
    } else {
      next(action);
      next(action);
      print("xxxxxxxxx 我是拦截器,过滤奇数");
    }
  }
}

class FlutterReduxDemoPage extends StatefulWidget {
  @override
  _FlutterReduxDemoPageState createState() => _FlutterReduxDemoPageState();
}

class _FlutterReduxDemoPageState extends State {
  /// 4、创建Store
  final store = Store(
    reducer,
    /// 拦截奇数
    middleware: [ReduxCountMiddleware()],
    initialState: ReduxCountState(0),
  );

  @override
  Widget build(BuildContext context) {
    return StoreProvider(
      store: store,
      child: Scaffold(
          appBar: AppBar(
            title: Text("redux"),
          ),
          body: Center(
            /// 5、关联Widget
            child: StoreConnector(
              converter: (store) => store.state.count.toString(),
              builder: (context, val) => Text(
                val,
                style: TextStyle(fontSize: 30, color: Colors.red),
              ),
            ),
          ),
        /// 6、触发
        floatingActionButton: StoreConnector(
          converter: (store) {
            return () => store.dispatch(ReduxAction.increment);
          },
          builder: (context, callback) {
            return FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: callback,
            );
          },
        ),
        ),
    );
  }
}


redux.gif

总结

redux内部是通过InheritedWidgetStream以及StreamBuilder的自定义封装。由于他的单向数据流思想,开发者的每个操作都只是一个Action,而这个行为所触发的逻辑完全有middlewarereducer决定,这样子的设计模式一定程度上将业务和UI进行了隔离,并且规范了整个事件流过程中的调用模式。

工具很强大,但是需要花费一定的学习成本,但是如果你之前是前端开发者,那么使用redux还是很得心应手的。但如果你之前更多的是App 开发,那可能Provider会更接近于你的使用方式。


Provide/Provider

Provider之前官方推荐的状态管理方式之一就是provide,它的特点不复杂、好理解、可控度高,但是后面就被Provider给替代了。


流程图

1、被设置到ChangeNotifierProviderChangeNotifier会被执行addListener,添加listener

2、listener内部会调用StateDelegateStateSetter方法,从而调用到StatefulWidgetsetState

3、当执行ChangeNotifiernotifyListeners,最终就会触发setState更新

image-20210326164059178.png


使用流程

  • 1、创建ChangeNotifier的子类,实现相关内部逻辑

  • 2、使用包括但不限于的:ChangeNotfierProviderMultiProviderprovider封装好的实体类把ChangeNotifier的子类加入到provider之中

  • 3、使用ConsummerProvider.of(context)等引用provider的值进行关联

    如果只是想获取provider的值,并不想根据状态进行更新需要使用Provider.of(context, listen: false)来获取到Provider

  • 4、调用ChangeNotifier的子类的notifyListeners方法触发更新


demo

之所以说简单,我们通过官方的数字累加Demo就可以看出

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class ProviderDemoPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => _CountProvider(),
      child: Scaffold(
        appBar: AppBar(title: Text("Provider")),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Builder(
              builder: (context) {
                return Text(
                  Provider.of<_CountProvider>(context).count.toString(),
                  style: TextStyle(fontSize: 30, color: Colors.orangeAccent),
                );
              },
            ),
            Consumer<_CountProvider>(
              builder: (context, provider, child) {
                return Center(
                  child: Text(
                    provider.count.toString(),
                    style: TextStyle(fontSize: 30, color: Colors.orangeAccent),
                  ),
                );
              },
            )
          ],
        ),
        floatingActionButton: Builder(
          builder: (context) => FloatingActionButton(
            onPressed: () {
              Provider.of<_CountProvider>(context, listen: false)?.add();
            },
            child: Icon(Icons.add),
          ),
        ),
      ),
    );
  }
}

class _CountProvider extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void add() {
    _count++;
    notifyListeners();
  }
}


总结

Provider提供了一种简单、不复杂,可控性好的状态管理方式,通过MultiProviders可以在一个页面中插入多个Provider,在配合Consumer使用可以实现颗粒度级别的刷新,避免造成不必要的性能浪费。也是目前主流的状态管理方式之一。

你可能感兴趣的:(Flutter - 状态管理)