Flutter 使用 Provider 进行状态管理(主题模式切换示例)

状态管理

Flutter页面是有widget构成的,在widget中就有有状态 widget无状态 widget。这里的状态是State,翻看源码可以看到State的描述是这样的:(1)在构建widget时可以同步读取(2)在widget的生存期内可能会更改。widget实现者的职责是确保使用State.setState在状态改变时迅速得到通知。因此状态管理就是在state变化时迅速通知到widget的实现着,让其根据通知做出相应的处理。

上篇文章(Flutter widget 间传递数据的方案 InheritedWidget、Notification、EventBus )我们了解了数据在widget之间的传递,文中的方案对状态的管理耦合度都比较高。

早就听闻官方推荐的 Provider 库和 阿里出品的 fish-redux 库通过本篇我们来学习大厂们是如何对state进行管理的。

Provider

Provider 4.0.5:依赖项注入(DI)和状态管理之间的混合,使用widgets构建。

通过使用widgets进行状态管理,provider可以保证:

  • 可维护性,通过强制的单向数据流实现
  • 可测试性/可组合性,因为始终可以模拟/覆盖值
  • 健壮性,因为很难忘记处理模型/小部件的更新方案

使用文档

暴露(创建)值

创建新的对象
  • 推荐做法:在create内创建一个新对象
Provider(
  create: (_) => new MyModel(),
  child: ...
)
  • 不推荐做法:使用Provider.value创建对象。
ChangeNotifierProvider.value(
  value: new MyModel(),
  child: ...
)

不要使用随时间变化的变量来创建对象。原因是在这种情况下,变量更改时将永远不会更新您的对象

int count;

Provider(
  create: (_) => new MyModel(count),
  child: ...
)

如果要将随时间变化的变量传递给对象,请考虑使用ProxyProvider

int count;

ProxyProvider0(
  update: (_, __) => new MyModel(count),
  child: ...
)
使用已存在的对象

如果已经有对象实例并想要公开它,则应使用提供程序的.value构造函数。否则,可能会在对象仍在使用时调用其dispose方法。

  • 推荐做法:使用ChangeNotifierProvider.value提供一个现有的ChangeNotifier
MyChangeNotifier variable;

ChangeNotifierProvider.value(
  value: variable,
  child: ...
)
  • 不推荐做法:使用默认构造函数重用现有的ChangeNotifier
MyChangeNotifier variable;

ChangeNotifierProvider(
  create: (_) => variable,
  child: ...
)

读取值

读取值的最简单方法是使用静态方法Provider.of <T>(BuildContext context)。该方法从传递BuildContextwidget开始,在widget tree中查找如果找到,它将返回找到的T类型的最近变量(如果未找到,则抛出该异常)。

结合暴露一个值的第一个示例,此widget将读取显示的String并呈现“ Hello World”

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(
      // String 类型
      Provider.of<String>(context)
    );
  }
}

另外,也可以不使用Provider.of,而可以使用ConsumerSelector。 这些对于性能优化或难以获得providerBuildContext后代很有用。有关更多信息,请参见FAQ或 ConsumerSelector 的文档。

读取多个值 MultiProvider

在大型应用程序中注入许多值时,Provider可能会迅速嵌套,如下:

Provider<Something>(
  create: (_) => Something(),
  child: Provider<SomethingElse>(
    create: (_) => SomethingElse(),
    child: Provider<AnotherThing>(
      create: (_) => AnotherThing(),
      child: someWidget,
    ),
  ),
),

这种死亡嵌套,看着就让人入魔,所以Provider也提供了简便的写法:

MultiProvider(
  providers: [
    Provider<Something>(create: (_) => Something()),
    Provider<SomethingElse>(create: (_) => SomethingElse()),
    Provider<AnotherThing>(create: (_) => AnotherThing()),
  ],
  child: someWidget,
)
ProxyProvider

3.0.0开始,提供了一种新的providerProxyProviderProxyProvider是一个provider,它将来自其他provider的多个值组合到一个新对象中,并将结果发送给Provider。 然后,只要新provider之一依赖更新,该新对象就会被更新。

下面的示例使用ProxyProvider来基于来自另一个provider的计数器来构建转换。

Widget build(BuildContext context) {
  return MultiProvider(
    providers: [
      ChangeNotifierProvider(create: (_) => Counter()),
      ProxyProvider<Counter, Translations>(
        update: (_, counter, __) => Translations(counter.value),
      ),
    ],
    child: Foo(),
  );
}

class Translations {
  const Translations(this._value);

  final int _value;

  String get title => 'You clicked $_value times';
}

它具有多种变体,例如:

  • ProxyProvider vs ProxyProvider2 vs ProxyProvider3,… 类名后面的数字是ProxyProvider依赖的其他providers的数量。
  • ProxyProvider vs ChangeNotifierProxyProvider vs ListenableProxyProvider,… 它们的工作方式都相似,但是ChangeNotifierProxyProvider不会将结果发送到Provider,而是将其值发送到ChangeNotifierProvider

使用文档总结

首先Provider是依赖注入和状态管理和混合物,也就是它能实现这两种功能。依赖注入这里先暂时不用管,状态管理才是文章的重点。

这里的状态管理主要是指数据、UI的管理。

看了以上文档,如果还是懵逼,那么直接看这里的使用步骤吧。

Provider简单的使用可以按如下步骤来:

  • 创建继承自 ChangeNotifier 的数据类
  • 创建值,创建新对象通过 create的方式,复用对象通过ChangeNotifierProvider.value的方式
  • 获取值,可通过Provider.of(context)的方式或者ConsumerSelector的方式

简单示例使用

官方 simple实现的效果是:点击 + 号按钮,标题文本和内容文本都会更新显示次数。 为了更好的理解Provider的使用,可看下面的代码

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

class ProviderTestPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
  // 使用 ChangeNotifierProvider.value 提供一个现有的ChangeNotifier 创建 Provider 
    return ChangeNotifierProvider.value(
      value: Counter(),
      child: MaterialApp(
        home: const MyHomePage(),
      )
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Title()),
      body: const Center(child: CounterLabel()),
      floatingActionButton: const IncrementCounterButton(),
    );
  }
}
/// + 号按钮 widget
class IncrementCounterButton extends StatelessWidget {
  const IncrementCounterButton({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () {
        // listen: false 必须设置
        Provider.of<Counter>(context,listen: false).increment();
      },
      tooltip: 'Increment',
      child: const Icon(Icons.add),
    );
  }
}
/// 文本内容 widget
class CounterLabel extends StatelessWidget {
  const CounterLabel({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<Counter>(context);
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        const Text(
          'You have pushed the button this many times:',
        ),
        Text(
          '${counter.count}',
          // ignore: deprecated_member_use
          style: Theme.of(context).textTheme.display1,
        ),
      ],
    );
  }
}

/// 标题 widget
class Title extends StatelessWidget {
  const Title({Key key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<Counter>(context);
    return Text('Tapped ${counter.count} times');
  }
}
/// 1. 数据类
class Counter with ChangeNotifier {
  int _count = 0;
  int get count => _count;

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

使用 Provider 实现主题切换示例

上篇文章写过 EventBus 实现主题切换 https://blog.csdn.net/ITxiaodong/article/details/105083216,这次使用 Provider 也实现主题切换的功能。

完成下面几个步骤即可实现:

  1. 定义类extends或者with ChangeNotifier,如:点击查看 theme.dart 的实现
  2. 封装Store类,用于所有provider数据的初始化。如:点击查看 store.dart 的实现
  3. main.dart文件中声明为全局的对象,并通过Consumer去调用。如:点击查看 main.dart 的实现
  4. 我的页面点击修改主题后出现弹窗并选择颜色。如:点击查看 mine_page.dart 的实现

为了阅读体验,不粘贴代码了。
具体源码请跳转项目查看:https://github.com/YGragon/Flutter-WanAndroid

参考

上车

佛系原创号主
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章