Flutter状态管理Provider(一) 简易使用

4,261 阅读3分钟

前言

如果对Provider的使用已经很熟练,可以跳过这部分内容,直接看过程分析部分 Flutter状态管理Provider(二)过程分析

简介

官网介绍

[Provider](https://github.com/rrousselGit/provider)

简单翻译来说,Provider:依赖注入与状态管理的结合,并且对外提供Widget使用。使用widget代替纯Dart对象,比如说Stream。因为widget简单,强大且可扩展。使用Provider,可以保证:可维护性(强制的单向数据流),可测试性,健壮性等等。

什么是状态?什么是状态管理?

  • 如果说页面是静态,拿到数据(状态),渲染完就完事了。压根就不需要状态管理。
  • 不幸的是,页面不可能全是静态。页面需要响应数据(状态)变化(网络数据?用户操作产生的数据?)更新UI。
  • 同时,数据变化的影响,不仅仅是组件内,还有可能是页面内其他组件,甚至于应用内其他页面。

UI=f(state)。从状态变化到UI更新的过程,我们称之为状态管理。状态管理要尽可能的把这个过程独立出来,让动态界面如同静态页面一般简单。

有哪些状态管理的方式/库

- setState
- FutureBuilder/StreamBuilder/BLoc
- Provider/ScopedModel
- redux/Fish-redux

简易版Provider实现

有了状态管理的介绍,我们可以参考Provider,通过手上现有的组件,实现一个简易版的Provider。要用到的系统组件:

  • setState:StatefulWidget现成的,刷新UI的办法
  • InheritedWidget:一个基础widget,让widget树具备从上而下传递数据的能力。同时数据变化可以引起依赖它的widget重新构建。
  • ChangeNotifier:观察者模式的代码模型。

setState

状态管理最基础的一个实现

class _SetStateDemoWidgetState extends State<SetStateDemoWidget> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(setStateDemoTitle),),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text("计数:$count"),
            RaisedButton(
              child: Text("increment"),
              onPressed: () => setState(() => count++),
            )
          ],
        ),
      ),
    );
  }
}

InheritedWidget

代码入口

用CountProvider继承InheritedWidget来保存数据。 通过context.getElementForInheritedWidgetOfExactType拿到CountProvider中的数据。 这个context一定要注意,只能用CountProvider子widget的BuildContext。CountProvider的查找是通过context往上的。 注意这个地方,我们只是单纯的拿数据,还没有用到InheritedWidget可以控制子widget重新构建。

///首先我们要有个地方存放我们的数据
class CountProvider extends InheritedWidget {
  final int count;

  const CountProvider({Key? key, required this.count, required Widget child})
      : super(key: key, child: child);

  @override
  bool updateShouldNotify(CountProvider old) {
    return true;
  }
}

class ProviderDemoWidget1 extends StatefulWidget {
 ...
}

class _ProviderDemoWidget1State extends State<ProviderDemoWidget1> {
  int _count = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(providerDemoTitle1),
      ),
      body: CountProvider(
        count: _count,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Builder(
                builder: (context2) {
                  /// 读取和显示计数
                  CountProvider provider = (context2
                      .getElementForInheritedWidgetOfExactType<CountProvider>()
                      ?.widget) as CountProvider;
                  return Text("计数:${provider.count}");
                },
              ),
              ElevatedButton(
                child: const Text("increment"),
                onPressed: () => setState(() => _count++),
              ),
              const Text(providerDemoIntroduction1),
            ],
          ),
        ),
      ),
    );
  }
}

Provider(InheritedWidget + ChangeNotifier)

我们先看效果图,我们看到有三种场景。只有依赖的组件更新了UI。

代码入口 CountModel封装示例1中的count,继承ChangeNotifier,具备notify能力。

class CountModel extends ChangeNotifier {
  int count;

  CountModel(this.count);

  void increment() {
    count++;
    notifyListeners();
  }
}

Provider继承InheritedWidget,封装两种访问:

  • context.dependOnInheritedWidgetOfExactType
  • context.getElementForInheritedWidgetOfExactType

区别在于,当InheritedWidget重新构建时,前者widget会重新构建,后者则不会。

class CountProvider extends InheritedWidget {
  final CountModel model;

  const CountProvider({Key? key, required this.model, required Widget child}) : super(key: key, child: child);

  static CountModel of(BuildContext context, bool depend) {
    var provider = depend
        ? context.dependOnInheritedWidgetOfExactType<CountProvider>()
        : context.getElementForInheritedWidgetOfExactType<CountProvider>()?.widget;
    return (provider as CountProvider).model;
  }

  @override
  bool updateShouldNotify(CountProvider old) {
    return true;
  }
}

ChangeNotifierProvider通过监听ChangeNotifier,将setState封装在其内部。总之,把状态管理(数据修改和Widget刷新)封装在自定义的对象内。外部控件不需要再关心状态管理的细节了。

class ChangeNotifierProvider extends StatefulWidget {
  final Widget child;

  final CountModel model;

  ChangeNotifierProvider({Key? key, required this.child, required this.model}) : super(key: key);

  @override
  _ChangeNotifierProviderState createState() => _ChangeNotifierProviderState();
}

class _ChangeNotifierProviderState extends State<ChangeNotifierProvider> {
  _ChangeNotifierProviderState();

  _update() {
    setState(() => {});
  }

  @override
  void initState() {
    super.initState();
    widget.model.addListener(_update);
  }

  @override
  void dispose() {
    super.dispose();
    widget.model.removeListener(_update);
  }

  @override
  Widget build(BuildContext context) {
    return CountProvider(
      model: widget.model,
      child: widget.child,
    );
  }
}

有了 CountModel,Provider,ChangeNotifierProvider,简易版状态管理Provider库也就写好了,下面用起来:

class ProviderDemoWidget2 extends StatefulWidget {
...
}

class _ProviderDemoWidget2State extends State<ProviderDemoWidget2> {
  CountModel _countModel = CountModel(0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text(providerDemoTitle2)),
      body: ChangeNotifierProvider(
        model: _countModel,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text("计数:${_countModel.count}"),
              Builder(builder: (context1) {
                return Text("计数:${CountProvider.of(context1, true).count}(有依赖情况)");
              }),
              Builder(builder: (context2) {
                return Text("计数:${CountProvider.of(context2, false).count}(无依赖情况)");
              }),
              ElevatedButton(child: const Text("increment"), onPressed: () => _countModel.increment()),
              const Text(providerDemoIntroduction2),
            ],
          ),
        ),
      ),
    );
  }
}

基于上个版本的通用泛型封装

上一个示例中CountModel不具有通用性,所以我们写一个泛型版本 代码入口

class Provider<T extends ChangeNotifier> extends InheritedWidget {
  final T model;

  Provider({Key? key, required this.model, required Widget child}) : super(key: key, child: child);

  static T of<T extends ChangeNotifier>(BuildContext context, bool depend) {
    var provider = depend
        ? context.dependOnInheritedWidgetOfExactType<Provider<ChangeNotifier>>()
        : context.getElementForInheritedWidgetOfExactType<Provider<ChangeNotifier>>()?.widget;
    return (provider as Provider<ChangeNotifier>).model as T;
  }

  @override
  bool updateShouldNotify(Provider old) {
    return true;
  }
}
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
  final Widget child;

  final T model;

  ChangeNotifierProvider({Key? key, required this.child, required this.model}) : super(key: key);

  @override
  _ChangeNotifierProviderState createState() => _ChangeNotifierProviderState();
}

class _ChangeNotifierProviderState extends State<ChangeNotifierProvider> {
  _ChangeNotifierProviderState();

  _update() {
    setState(() => {});
  }

  @override
  void initState() {
    super.initState();
    widget.model.addListener(_update);
  }

  @override
  void dispose() {
    super.dispose();
    widget.model.removeListener(_update);
  }

  @override
  Widget build(BuildContext context) {
    return Provider(
      model: widget.model,
      child: widget.child,
    );
  }
}

这样一个简单的ChangeNotifierProvider就实现了。 demo使用

class ProviderDemoWidget3 extends StatefulWidget {
  ...
}

class _ProviderDemoWidget3State extends State<ProviderDemoWidget3> {
  CountModel _countModel = CountModel(0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar( title: const Text(providerDemoTitle3)),
      body: ChangeNotifierProvider<CountModel>(
        model: _countModel,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Builder(builder: (context1) {
                return Text("计数:${Provider.of<CountModel>(context1, true).count}(有依赖情况)");
              }),
              Builder(builder: (context2) {
                return Text("计数:${Provider.of<CountModel>(context2, false).count}(无依赖情况)");
              }),
              ElevatedButton(child: const Text("increment"), onPressed: () => _countModel.increment()),
              const Text(providerDemoIntroduction3),
            ],
          ),
        ),
      ),
    );
  }
}
class ProviderDemoWidget3 extends StatefulWidget {
  ...
}

class _ProviderDemoWidget3State extends State<ProviderDemoWidget3> {
  CountModel _countModel = CountModel(0);

  @override
  Widget build(BuildContext context) {
    ...仅修改访问部分,增加泛型支持
    Builder(builder: (context1) {
       return Text("计数:${Provider.of<CountModel>(context1, true).count}(有依赖情况)");
    }),
    Builder(builder: (context2) {
        return Text("计数:${Provider.of<CountModel>(context2, false).count}(无依赖情况)");
   }),
    ...
  }
}

真正Provider的用法

举例Provider库中ChangeNotifierProvider使用。

其中使用到了Consumer,通过Provider.of(context)封装对model的访问

正如介绍所描述的非常简单。当然了Provider库,可维护性,可测试性,可扩展性远比我们所写的强大。

class ProviderDemoWidget4 extends StatefulWidget {
 ...
}

class _ProviderDemoWidget4State extends State<ProviderDemoWidget4> {
  final CountModel _countModel = CountModel(0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text(providerDemoTitle4)),
      body: ChangeNotifierProvider.value(
        value: _countModel,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Consumer<CountModel>(
                builder: (contextC, model, child) {
                  return Text("计数:${model.count}(有依赖情况)");
                },
              ),
              Builder(builder: (context2) {
                return Text("计数:${Provider.of<CountModel>(context2, listen: false).count}(无依赖情况)");
              }),
              ElevatedButton(child: const Text("increment"), onPressed: () => _countModel.increment()),
              const Text(providerDemoIntroduction4),
            ],
          ),
        ),
      ),
    );
  }
}

到此为止,我们也就了解了Provider的基本原理和基本使用。 可我们的征程才开始。知其然,更要知其所以然。接下来,我们从setState开始一步步分析Provider状态管理的过程。

下一篇:Flutter状态管理Provider(二)过程分析

参考资料

Flutter实战