美文网首页All in FlutterAndroid进阶之路Flutter圈子
Flutter状态管理provider的使用和封装

Flutter状态管理provider的使用和封装

作者: A_si | 来源:发表于2020-06-01 00:44 被阅读0次

    Flutter状态管理provider的使用和封装

    Flutter提供了InheritedWidget类,帮助我们处理父子组件之间的状态管理。provider是InheritedWidget的封装,让开发者易于使用和复用。但是初看provider的文档,有点让人头大:

    name description
    Provider The most basic form of provider. It takes a value and exposes it, whatever the value is.
    ListenableProvider A specific provider for Listenable object. ListenableProvider will listen to the object and ask widgets which depend on it to rebuild whenever the listener is called.
    ChangeNotifierProvider A specification of ListenableProvider for ChangeNotifier. It will automatically call ChangeNotifier.dispose when needed.
    ValueListenableProvider Listen to a ValueListenable and only expose ValueListenable.value.
    StreamProvider Listen to a Stream and expose the latest value emitted.
    FutureProvider Takes a Future and updates dependents when the future completes.

    不是说provider是易于使用吗?我只想以一种的简单的方式管理状态,却给我这么多选择,到底我该选择哪个呢?选择困难症急的想薅头发。

    选择与使用

    新建Futter项目,更改默认的计数器布局,效果如下:


    Simulator Screen Shot - iPhone SE (2nd generation) - 2020-05-29 at 00.07.34.png

    点击FlatButton,更改应用程序的计数器状态,使计数器加1,前两行的text显示计数器状态最新值,FlatButton和两个text是不同部分的widget。

    1. 在的pubspec.yaml文件中依赖provider:
    dependencies:
      flutter:
        sdk: flutter
      provider: ^4.1.2
    
    1. 导入:
      import 'package:provider/provider.dart';

    Provider

    Provider是provider包中最基本的提供者widget类型。它可以给包括住的所有widget提供值,但是当该值改变时,并不会更新widget。

    新增MyModel类,作为要让Provider提供出去的值,把计数器的数值counter声明到这里,并且更改计数值的方法也放在这里,点击按钮的时候,调用MyModel对象的incrementCounter(),延时2秒并更改counter:

    class MyModel {
      
      MyModel({this.counter=0});
    
      int counter = 0;
    
      Future<void> incrementCounter() async {
        await Future.delayed(Duration(seconds: 2));
        counter++;
        print(counter);
      }
    }
    

    在widget树的顶部包裹Provider小部件,将MyModel对象通过Provider提供给widget树。然后使用了两种获取Provider提供值的方式,在Column里:

    1. 先使用Provider.of<MyModel>(context)获取到MyModel对象的引用;
    2. 然后使用Consumer小部件获得对MyModel对象的引用;
    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Provider(
          create: (_) => MyModel(),
          child: Scaffold(
            appBar: AppBar(
              title: Text('provider'),
            ),
            body: Column(
              children: <Widget>[
                Builder(
                  builder: (context) {
                    // 获取到provider提供出来的值
                    MyModel _model = Provider.of<MyModel>(context);
                    return Container(
                        margin: const EdgeInsets.only(top: 20),
                        width: MediaQuery.of(context).size.width,
                        padding: const EdgeInsets.all(20),
                        alignment: Alignment.center,
                        color: Colors.lightBlueAccent,
                        child: Text('当前是:${_model.counter}'));
                  },
                ),
                Consumer<MyModel>(
                    // 获取到provider提供出来的值
                  builder: (context, model, child) {
                    return Container(
                      margin: const EdgeInsets.only(top: 20),
                      width: MediaQuery.of(context).size.width,
                      padding: const EdgeInsets.all(20),
                      alignment: Alignment.center,
                      color: Colors.lightGreen,
                      child: Text(
                        '${model.counter}',
                      ),
                    );
                  },
                ),
                Consumer<MyModel>(
                   // 获取到provider提供出来的值
                  builder: (context, model, child) {
                    return FlatButton(
                        color: Colors.tealAccent,
                        onPressed:model.incrementCounter,
                        child: Icon(Icons.add));
                  },
                ),
              ],
            ),
          ),
        );
      }
    }
    
    

    点击FlatButton,model调用incrementCounter()函数,计数值加1。但是并不会重建UI,因为该Provider小部件不会监听其提供的值的更改。


    Simulator Screen Shot - iPhone SE (2nd generation) - 2020-05-29 at 00.07.34.png

    打印出计数值的变化


    Snipaste_2020-05-28_23-40-17.png

    ChangeNotifierProvider

    与最基础的Provider小部件不同,ChangeNotifierProvider会监听其提供出去的模型对象中的更改。当有值更改后,它将重建下方所有的Consumer和使用Provider.of<MyModel>(context)监听并获取提供值的地方。

    代码中更改Provider为ChangeNotifierProvider。MyModel混入ChangeNotifier(继承也一样)。然后更改counter之后调用notifyListeners(),这样ChangeNotifierProvider就会得到通知,并且Consumer和监听的地方将重建其小部件。

    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider(
          create: (_) => MyModel(),
          child: Scaffold(
            appBar: AppBar(
              title: Text('provider'),
            ),
            body: Column(
              children: <Widget>[
                Builder(
                  builder: (context) {
                    MyModel _model = Provider.of<MyModel>(context);
                    return Container(
                        margin: const EdgeInsets.only(top: 20),
                        width: MediaQuery.of(context).size.width,
                        padding: const EdgeInsets.all(20),
                        alignment: Alignment.center,
                        color: Colors.lightBlueAccent,
                        child: Text('当前是:${_model.counter}'));
                  },
                ),
                Consumer<MyModel>(
                  builder: (context, model, child) {
                    return Container(
                      margin: const EdgeInsets.only(top: 20),
                      width: MediaQuery.of(context).size.width,
                      padding: const EdgeInsets.all(20),
                      alignment: Alignment.center,
                      color: Colors.lightGreen,
                      child: Text(
                        '${model.counter}',
                      ),
                    );
                  },
                ),
                Consumer<MyModel>(
                  builder: (context, model, child) {
                    return FlatButton(
                        color: Colors.tealAccent,
                        onPressed: model.incrementCounter,
                        child: Icon(Icons.add));
                  },
                ),
              ],
            ),
          ),
        );
      }
    }
    
    class MyModel with ChangeNotifier{
      //                                               <--- MyModel
      MyModel({this.counter = 0});
    
      int counter = 0;
    
      Future<void> incrementCounter() async {
        await Future.delayed(Duration(seconds: 2));
        counter++;
        print(counter);
        notifyListeners();
      }
    }
    
    Simulator Screen Shot - iPhone SE (2nd generation) - 2020-05-29 at 00.21.22.png

    每次点击,都会更改计数器的值,如果第一行的计数值是保留初始值,不更新呢?很简单,把Provider.of的监听器设置为false,这样更改后就不会重新构建第一行的text:
    MyModel _model = Provider.of<MyModel>(context,listen: false);

    Simulator Screen Shot - iPhone SE (2nd generation) - 2020-05-29 at 00.25.13.png

    FutureProvider

    FutureProvider基本上只是普通FutureBuilder的包装。我们需要给它提供一些显示在UI中的初始数据,还要为它设置要提供值的Future。在Future完成的时候,FutureProvider会通知Consumer重建自己的小部件。

    在下面的代码中,使用了一个counter为0的MyModel向UI提供一些初始数据,并且添加了一个Future函数,可在3秒后返回一个counter为1的MyModel。
    和基类Provider一样,FutureProvider它不会监听模型本身内的任何更改。在下面的代码中依旧通过按钮点击事件使counter加1,但是对UI没有影响。

    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return FutureProvider(
          initialData: MyModel(counter: 0),
          create: (context) => someAsyncFunctionToGetMyModel(),
          child: Scaffold(
            appBar: AppBar(
              title: Text('provider'),
            ),
            body: Column(
              children: <Widget>[
                Builder(
                  builder: (context) {
                    MyModel _model = Provider.of<MyModel>(context, listen: false);
                    return Container(
                        margin: const EdgeInsets.only(top: 20),
                        width: MediaQuery.of(context).size.width,
                        padding: const EdgeInsets.all(20),
                        alignment: Alignment.center,
                        color: Colors.lightBlueAccent,
                        child: Text('当前是:${_model.counter}'));
                  },
                ),
                Consumer<MyModel>(
                  builder: (context, model, child) {
                    return Container(
                      margin: const EdgeInsets.only(top: 20),
                      width: MediaQuery.of(context).size.width,
                      padding: const EdgeInsets.all(20),
                      alignment: Alignment.center,
                      color: Colors.lightGreen,
                      child: Text(
                        '${model.counter}',
                      ),
                    );
                  },
                ),
                Consumer<MyModel>(
                  builder: (context, model, child) {
                    return FlatButton(
                        color: Colors.tealAccent,
                        onPressed: model.incrementCounter,
                        child: Icon(Icons.add));
                  },
                ),
              ],
            ),
          ),
        );
      }
    
      Future<MyModel> someAsyncFunctionToGetMyModel() async {
        //  <--- async function
        await Future.delayed(Duration(seconds: 3));
        return MyModel(counter: 1);
      }
    }
    
    class MyModel with ChangeNotifier {
      //                                               <--- MyModel
      MyModel({this.counter = 0});
    
      int counter = 0;
    
      Future<void> incrementCounter() async {
        await Future.delayed(Duration(seconds: 2));
        counter++;
        print(counter);
        notifyListeners();
      }
    }
    

    FutureProvider通过设置的Future完成后会通知Consumer,重新build。但是,Future完成后,点击按钮也不会更新UI。

    FutureProvider适用于没有刷新和变更的页面,和FutureBuilder一样的作用。

    StreamProvider

    StreamProvider基本上是StreamBuilder的包装,和上面的FutureProvider一样。不同的是StreamProvider提供的是流,FutureProvider需要的一个Future。

    StreamProvider也不会监听model本身的变化。它仅监听流中的新事件:

    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return StreamProvider(
          initialData: MyModel(counter: 0),
          create: (context) => getStreamOfMyModel(),
          child: Scaffold(
            appBar: AppBar(
              title: Text('provider'),
            ),
            body: Column(
              children: <Widget>[
                Builder(
                  builder: (context) {
                    MyModel _model = Provider.of<MyModel>(context, listen: false);
                    return Container(
                        margin: const EdgeInsets.only(top: 20),
                        width: MediaQuery.of(context).size.width,
                        padding: const EdgeInsets.all(20),
                        alignment: Alignment.center,
                        color: Colors.lightBlueAccent,
                        child: Text('当前是:${_model.counter}'));
                  },
                ),
                Consumer<MyModel>(
                  builder: (context, model, child) {
                    return Container(
                      margin: const EdgeInsets.only(top: 20),
                      width: MediaQuery.of(context).size.width,
                      padding: const EdgeInsets.all(20),
                      alignment: Alignment.center,
                      color: Colors.lightGreen,
                      child: Text(
                        '${model.counter}',
                      ),
                    );
                  },
                ),
                Consumer<MyModel>(
                  builder: (context, model, child) {
                    return FlatButton(
                        color: Colors.tealAccent,
                        onPressed: model.incrementCounter,
                        child: Icon(Icons.add));
                  },
                ),
              ],
            ),
          ),
        );
      }
    
      Stream<MyModel> getStreamOfMyModel() {
        return Stream<MyModel>.periodic(
            Duration(seconds: 1), (x) => MyModel(counter: x)).take(10);
      }
    }
    
    class MyModel with ChangeNotifier {
      //                                               <--- MyModel
      MyModel({this.counter = 0});
    
      int counter = 0;
    
      Future<void> incrementCounter() async {
        await Future.delayed(Duration(seconds: 2));
        counter++;
        print(counter);
        notifyListeners();
      }
    }
    

    给StreamProvider设置了一个每隔1秒更新一次的stream,ui上的计数值也是每隔一秒改变一次。但是点击按钮同样不会刷新ui。所以也可以认为是一个StreamBuilder。

    ValueListenableProvider

    ValueListenableProvider类似于ValueChange的封装,它的作用和ChangeNotifierProvider一样,在值改变的时候,会通知Consumer重新build,但是使用起来比ChangeNotifierProvider复杂,需要先用Provider提供MyModel给Consumer,然后把MyModel里的ValueNotifier给ValueListenableProvider:

    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Provider<MyModel>(
          create: (context) => MyModel(),
          child: Consumer<MyModel>(
            builder: (context, myModel, child) {
              return ValueListenableProvider<int>.value(
                value: myModel.counter,
                child: Scaffold(
                  appBar: AppBar(
                    title: Text('provider'),
                  ),
                  body: Column(
                    children: <Widget>[
                      Builder(
                        builder: (context) {
                          var count = Provider.of<int>(context);
                          return Container(
                              margin: const EdgeInsets.only(top: 20),
                              width: MediaQuery.of(context).size.width,
                              padding: const EdgeInsets.all(20),
                              alignment: Alignment.center,
                              color: Colors.lightBlueAccent,
                              child: Text('当前是:$count'));
                        },
                      ),
                      Consumer<int>(
                        builder: (context, value, child) {
                          return Container(
                            margin: const EdgeInsets.only(top: 20),
                            width: MediaQuery.of(context).size.width,
                            padding: const EdgeInsets.all(20),
                            alignment: Alignment.center,
                            color: Colors.lightGreen,
                            child: Text(
                              '$value',
                            ),
                          );
                        },
                      ),
                      Consumer<MyModel>(
                        builder: (context, model, child) {
                          return FlatButton(
                              color: Colors.tealAccent,
                              onPressed: model.incrementCounter,
                              child: Icon(Icons.add));
                        },
                      ),
                    ],
                  ),
                ),
              );
            }
          ),
        );
      }
    }
    
    class MyModel {
      ValueNotifier<int> counter = ValueNotifier(0);
      Future<void> incrementCounter() async {
        await Future.delayed(Duration(seconds: 2));
        print(counter.value++);
        counter.value = counter.value;
      }
    }
    

    ListenableProvider

    ListenableProvider和ChangeNotifierProvider一样,
    区别在于,如果Model是一个复杂模型ChangeNotifierProvider 会在你需要的时候,自动调用其 _disposer 方法,所以一般还是使用ChangeNotifierProvider即可。

    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return ListenableProvider<MyModel>(
          create: (context) => MyModel(),
          child: Scaffold(
            appBar: AppBar(
              title: Text('provider'),
            ),
            body: Column(
              children: <Widget>[
                Builder(
                  builder: (context) {
                    MyModel modol = Provider.of<MyModel>(context);
                    return Container(
                        margin: const EdgeInsets.only(top: 20),
                        width: MediaQuery.of(context).size.width,
                        padding: const EdgeInsets.all(20),
                        alignment: Alignment.center,
                        color: Colors.lightBlueAccent,
                        child: Text('当前是:${modol.counter}'));
                  },
                ),
                Consumer<MyModel>(
                  builder: (context, model, child) {
                    return Container(
                      margin: const EdgeInsets.only(top: 20),
                      width: MediaQuery.of(context).size.width,
                      padding: const EdgeInsets.all(20),
                      alignment: Alignment.center,
                      color: Colors.lightGreen,
                      child: Text(
                        '${model.counter}',
                      ),
                    );
                  },
                ),
                Consumer<MyModel>(
                  builder: (context, model, child) {
                    return FlatButton(
                        color: Colors.tealAccent,
                        onPressed: model.incrementCounter,
                        child: Icon(Icons.add));
                  },
                ),
              ],
            ),
          ),
        );
      }
    }
    
    class MyModel with ChangeNotifier {
      int counter = 0;
    
      Future<void> incrementCounter() async {
        await Future.delayed(Duration(seconds: 2));
        counter++;
        notifyListeners();
        print(counter);
      }
    }
    

    MultiProvider

    上面的示例都仅使用了一个Model对象。如果需要提供第二种类型的Model对象,可以嵌套Provider。但是,嵌套迷之缩进,可读性低。这时候使用MultiProvider非常简洁,

    我们改下上面的计数器,一般首页会有一个banner和列表。我们用上面的计数器模拟banner,下面的计数器模拟列表:

    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MultiProvider(
          providers: [
            ChangeNotifierProvider<BannerModel>(create: (context) => BannerModel()),
            ChangeNotifierProvider<ListModel>(create: (context) => ListModel()),
          ],
          child: Scaffold(
            appBar: AppBar(
              title: Text('provider'),
            ),
            body: Column(
              children: <Widget>[
                Builder(
                  builder: (context) {
                    BannerModel modol = Provider.of<BannerModel>(context);
                    return Container(
                        margin: const EdgeInsets.only(top: 20),
                        width: MediaQuery.of(context).size.width,
                        padding: const EdgeInsets.all(20),
                        alignment: Alignment.center,
                        color: Colors.lightBlueAccent,
                        child: Text('当前Banner有几个:${modol.counter}'));
                  },
                ),
                Consumer<ListModel>(
                  builder: (context, model, child) {
                    return Container(
                      margin: const EdgeInsets.only(top: 20),
                      width: MediaQuery.of(context).size.width,
                      padding: const EdgeInsets.all(20),
                      alignment: Alignment.center,
                      color: Colors.lightGreen,
                      child: Text(
                        '当前Banner有几个:${model.counter}',
                      ),
                    );
                  },
                ),
                Consumer<BannerModel>(
                  builder: (context, model, child) {
                    return FlatButton(
                        color: Colors.tealAccent,
                        onPressed: model.getBanner,
                        child: Text("获取banner"));
                  },
                ),
                Consumer<ListModel>(
                  builder: (context, model, child) {
                    return FlatButton(
                        color: Colors.tealAccent,
                        onPressed: model.getList,
                        child: Text("获取列表"));
                  },
                ),
              ],
            ),
          ),
        );
      }
    }
    
    class BannerModel with ChangeNotifier {
      int counter = 0;
    
      Future<void> getBanner() async {
        await Future.delayed(Duration(seconds: 2));
        counter++;
        notifyListeners();
        print(counter);
      }
    }
    
    class ListModel with ChangeNotifier {
      int counter = 0;
    
      Future<void> getList() async {
        await Future.delayed(Duration(seconds: 2));
        counter++;
        notifyListeners();
        print(counter);
      }
    }
    
    MultiProvider

    按下banner按钮,就单独获取banner的数值,并更新banner的Consumer。列表的同理。

    ProxyProvider

    如果要提供两个Model,但是其中一个Model取决于另一个Model,在这种情况下,可以使用ProxyProvider。A ProxyProvider从一个Provider获取值,然后将其注入另一个Provider,

    把上面的改下,比如的上传图片功能,需要先把图片提交到图片服务器,然后再把链接发送到后台服务器:

    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MultiProvider(
          providers: [
            ChangeNotifierProvider<PicModel>(create: (context) => PicModel()),
            ProxyProvider<PicModel, SubmitModel>(
              update: (context, myModel, anotherModel) => SubmitModel(myModel),
            ),
          ],
          child: Scaffold(
            appBar: AppBar(
              title: Text('provider'),
            ),
            body: Column(
              children: <Widget>[
                Builder(
                  builder: (context) {
                    PicModel modol = Provider.of<PicModel>(context);
                    return Container(
                        margin: const EdgeInsets.only(top: 20),
                        width: MediaQuery.of(context).size.width,
                        padding: const EdgeInsets.all(20),
                        alignment: Alignment.center,
                        color: Colors.lightBlueAccent,
                        child: Text('提交图片:${modol.counter}'));
                  },
                ),
    
                Consumer<PicModel>(
                  builder: (context, model, child) {
                    return FlatButton(
                        color: Colors.tealAccent,
                        onPressed: model.upLoadPic,
                        child: Text("提交图片"));
                  },
                ),
                Consumer<SubmitModel>(
                  builder: (context, model, child) {
                    return FlatButton(
                        color: Colors.tealAccent,
                        onPressed: model.subMit,
                        child: Text("提交"));
                  },
                ),
              ],
            ),
          ),
        );
      }
    }
    
    class PicModel with ChangeNotifier {
      int counter = 0;
    
      Future<void> upLoadPic() async {
        await Future.delayed(Duration(seconds: 2));
        counter++;
        notifyListeners();
        print(counter);
      }
    }
    
    class SubmitModel {
      PicModel _model;
    
      SubmitModel(this._model);
    
      Future<void> subMit() async {
        await _model.upLoadPic();
      }
    }
    

    基于MVVM模式封装Provider

    相信大家都已经理解provider的流程,如下图:


    MVVM

    上面已经演示完了Provider的用法,在开发中,我们需要Model充当ViewModel,处理业务逻辑,但是每次都写样板代码的话也很麻烦,所以需要封装下,易于使用。


    Simulator Screen Shot - iPhone SE (2nd generation) - 2020-05-31 at 23.35.55.png
    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider<LoginViewModel>(
          create: (BuildContext context) {
            return LoginViewModel(loginServive: LoginServive());
          },
          child: Scaffold(
            appBar: AppBar(
              title: Text('provider'),
            ),
            body: Column(
              children: <Widget>[
                Consumer<LoginViewModel>(
                  builder: (context, model, child) {
                    return Text(model.info);
                  },
                ),
                Consumer<LoginViewModel>(
                  builder: (context, model, child) {
                    return FlatButton(
                        color: Colors.tealAccent,
                        onPressed: () => model.login("pwd"),
                        child: Text("登录"));
                  },
                ),
              ],
            ),
          ),
        );
      }
    }
    
    /// viewModel
    class LoginViewModel extends ChangeNotifier {
      LoginServive _loginServive;
      String info = '请登录';
    
      LoginViewModel({@required LoginServive loginServive})
          : _loginServive = loginServive;
    
      Future<String> login(String pwd) async {
        info = await _loginServive.login(pwd);
        notifyListeners();
      }
    }
    
    /// api
    class LoginServive {
      static const String Login_path = 'xxxxxx';
    
      Future<String> login(String pwd) async {
        return new Future.delayed(const Duration(seconds: 1), () => "登录成功");
      }
    }
    

    这种页面写法,基本每个页面都要,下面我们一步一步开始封装。

    1. 一般页面载入的时候会显示一个loading,然后加载成功展示数据,失败就展示失败页面,所以枚举一个页面状态:
    enum ViewState { Loading, Success,Failure }
    
    1. ViewModel都会在页面状态属性改变后更新ui,通常会调用notifyListeners,把这一步移到BaseModel中:
    class BaseModel extends ChangeNotifier {
      ViewState _state = ViewState.Loading;
    
      ViewState get state => _state;
    
      void setState(ViewState viewState) {
        _state = viewState;
        notifyListeners();
      }
    }
    
    1. 我们知道ui里需要ChangeNotifierProvider提供Model,并且用Consumer更新ui。因此我们也将其内置到BaseView中:
    class BaseWidget<T extends ChangeNotifier> extends StatefulWidget {
      final Widget Function(BuildContext context, T value, Widget child) builder;
      final T model;
      final Widget child;
    
      BaseWidget({Key key, this.model, this.builder, this.child}) : super(key: key);
    
      @override
      State<StatefulWidget> createState() => _BaseWidgetState();
    
    }
    
    class _BaseWidgetState<T extends ChangeNotifier> extends State<BaseWidget<T>> {
    
      T model;
    
      @override
      void initState() {
        model = widget.model;
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider<T>.value(
          value: model,
          child: Consumer<T>(
            builder: widget.builder,
            child: widget.child,
          ),
        );
      }
    }
    
    1. 有时候我们的页面数据只是局部更新,Consumer的child属性就是模型更改时不需要重建的UI,所以我们将需要更新的ui放在builder里,不需要更新的写在child里:
    Consumer<LoginViewModel>(
      // Pass the login header as a prebuilt-static child
      child: LoginHeader(controller: _controller),
      builder: (context, model, child) => Scaffold(
        ...
        body: Column (
    
          children: [
    //不更新的部分
            child,
            ...
          ]
        )
    
    1. 大多时候,我们已进入一个页面,就要获取数据,所以我们也把这个操作移入基类:
    class BaseWidget<T extends ChangeNotifier> extends StatefulWidget {
    
    final Function(T) onModelReady;
    ...
    
     BaseWidget({
       ...
        this.onModelReady,
      });
      ...
    }
    
    ...
    
    @override
    void initState() {
      model = widget.model;
    
      if (widget.onModelReady != null) {
        widget.onModelReady(model);
      }
    
      super.initState();
    }
    

    现在,我们用封装的基类完成登录页面:

    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return BaseWidget<LoginViewModel>(
          model: LoginViewModel(loginServive: LoginServive()),
          builder: (context, model, child) => Scaffold(
            appBar: AppBar(
              title: Text('provider'),
            ),
            body: Column(
              children: <Widget>[
                model.state == ViewState.Loading
                    ? Center(
                        child: CircularProgressIndicator(),
                      )
                    : Text(model.info),
                FlatButton(
                    color: Colors.tealAccent,
                    onPressed: () => model.login("pwd"),
                    child: Text("登录")),
              ],
            ),
          ),
        );
      }
    }
    
    /// viewModel
    class LoginViewModel extends BaseModel {
      LoginServive _loginServive;
      String info = '请登录';
    
      LoginViewModel({@required LoginServive loginServive})
          : _loginServive = loginServive;
    
      Future<String> login(String pwd) async {
        setState(ViewState.Loading);
        info = await _loginServive.login(pwd);
        setState(ViewState.Success);
      }
    }
    
    /// api
    class LoginServive {
      static const String Login_path = 'xxxxxx';
    
      Future<String> login(String pwd) async {
        return new Future.delayed(const Duration(seconds: 1), () => "登录成功");
      }
    }
    
    enum ViewState { Loading, Success, Failure, None }
    
    class BaseModel extends ChangeNotifier {
      ViewState _state = ViewState.None;
    
      ViewState get state => _state;
    
      void setState(ViewState viewState) {
        _state = viewState;
        notifyListeners();
      }
    }
    
    class BaseWidget<T extends ChangeNotifier> extends StatefulWidget {
      final Widget Function(BuildContext context, T model, Widget child) builder;
      final T model;
      final Widget child;
      final Function(T) onModelReady;
    
      BaseWidget({
        Key key,
        this.builder,
        this.model,
        this.child,
        this.onModelReady,
      }) : super(key: key);
    
      _BaseWidgetState<T> createState() => _BaseWidgetState<T>();
    }
    
    class _BaseWidgetState<T extends ChangeNotifier> extends State<BaseWidget<T>> {
      T model;
    
      @override
      void initState() {
        model = widget.model;
    
        if (widget.onModelReady != null) {
          widget.onModelReady(model);
        }
    
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider<T>(
          create: (BuildContext context) => model,
          child: Consumer<T>(
            builder: widget.builder,
            child: widget.child,
          ),
        );
      }
    }
    

    相关文章

      网友评论

        本文标题:Flutter状态管理provider的使用和封装

        本文链接:https://www.haomeiwen.com/subject/kgstzhtx.html