Flutter状态管理1-ChangeNotifierProvider的使用

关于Flutter中的状态管理,可以参考官网的介绍:Simple app state management

中文网的介绍:简单的应用状态管理

Flutter 官方的两个sample:
provider_counter
provider_shopper

ChangeNotifierProvider的使用

0. 添加provider包的依赖

在pubspec.yaml中添加provider:

dependencies:
  flutter:
    sdk: flutter

  # Import the provider package.
  provider: ^3.0.0

provider包的文档:
https://pub.dev/documentation/provider/latest/
里面介绍来从2.0版本到3.0版本的迁移修改方法,
在3.0版本中,有以下Provider类型:
在这里插入图片描述
后续博文将逐个介绍学习各Provider的使用场景

1. 定义一个数据Model类,继承自ChangeNotifier

/// Simplest possible model, with just one field.
///
/// [ChangeNotifier] is a class in `flutter:foundation`. [Counter] does
/// _not_ depend on Provider.
class Counter with ChangeNotifier {
  int value = 0;

  void increment() {
    value += 1;
    notifyListeners();
  }
}

这里的ChangeNotifier其实很简单,类似于一个Observable,继承自Listenable,内部维护了一个
ObserverList <VoidCallback> _listeners,提供添加和删除listener的方法,有一个notify方法,用来通知所有的观察者(或者称为消费者Consumer)
大致源码如下:

class ChangeNotifier implements Listenable {
  ObserverList<VoidCallback> _listeners = ObserverList<VoidCallback>();

//省略addListener、removeListener等方法

 @protected
  @visibleForTesting
  void notifyListeners() {
    assert(_debugAssertNotDisposed());
    if (_listeners != null) {
      final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
      for (VoidCallback listener in localListeners) {
        try {
          if (_listeners.contains(listener))
            listener();
        } catch (exception, stack) {
          FlutterError.reportError(FlutterErrorDetails(
            exception: exception,
            stack: stack,
            library: 'foundation library',
            context: ErrorDescription('while dispatching notifications for $runtimeType'),
            informationCollector: () sync* {
              yield DiagnosticsProperty<ChangeNotifier>(
                'The $runtimeType sending notification was',
                this,
                style: DiagnosticsTreeStyle.errorProperty,
              );
            },
          ));
        }
      }
    }
  }


2. 使用ChangeNotifierProvider来将Model与Widget相关联

这里的ChangeNotifierProvider包裹的Widget选哪个要注意一下:

⚠️注意:ChangeNotifierProvider 放在什么位置:在需要访问它的 widget 之上,但是不要把ChangeNotifierProvider放的级别太高,因为你不希望破坏整个结构

 runApp(
    // Provide the model to all widgets within the app. We're using
    // ChangeNotifierProvider because that's a simple way to rebuild
    // widgets when a model changes. We could also just use
    // Provider, but then we would have to listen to Counter ourselves.
    //
    // Read Provider's docs to learn about all the available providers.
    ChangeNotifierProvider(
      // Initialize the model in the builder. That way, Provider
      // can own Counter's lifecycle, making sure to call `dispose`
      // when not needed anymore.
      builder: (context) => Counter(),
      child: MyApp(),
    ),
  );

请注意我们定义了一个 builder 来创建一个 Model 的实例。ChangeNotifierProvider 非常聪明,它 不会 重复实例化 Model,除非在个别场景下。如果该实例已经不会再被调用,ChangeNotifierProvider 也会自动调用 Model 的 dispose() 方法。

如果你想提供更多状态,可以使用 MultiProvider:

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(builder: (context) => CartModel()),
        Provider(builder: (context) => SomeOtherClass()),
      ],
      child: MyApp(),
    ),
  );
}

3. 定义监听者Consumer,获取Model的值来更新UI

 body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            // Consumer looks for an ancestor Provider widget
            // and retrieves its model (Counter, in this case).
            // Then it uses that model to build widgets, and will trigger
            // rebuilds if the model is updated.
            Consumer<Counter>(
              builder: (context, counter, child) => Text(
                '${counter.value}',
                style: Theme.of(context).textTheme.display1,
              ),
            ),
          ],
        ),
      )

Consumer widget 唯一必须的参数就是 builder。当 ChangeNotifier 发生变化的时候会调用 builder 这个函数。(换言之,当你在模型中调用 notifyListeners() 时,所有和 Consumer 相关的 builder 方法都会被调用。)

builder 函数的第二个参数是 ChangeNotifier 的实例。它是我们最开始就能得到的实例。你可以通过该实例定义 UI 的内容。

第三个参数是 child,用于优化目的。如果 Consumer 下面有一个庞大的子树,当模型发生改变的时候,该子树 并不会 改变,那么你就可以仅仅创建它一次,然后通过 builder 获得该实例。

⚠️注意:最好能把 Consumer 放在 widget 树尽量低的位置上。你总不希望 UI 上任何一点小变化就全盘重新构建 widget 吧

4. 使用Provider.of来更新数据

除了像上一步中使用Consumer可以获取到当前的model数据之外,还可以使用Provider.of;
但是使用Provider.of,默认使用时,Model中的 notifyListeners() 被调用后,所有这个Model关联的Consumer的build方法都会被调用,也就是会刷新相关联的Widget;

floatingActionButton: FloatingActionButton(
        // Provider.of is another way to access the model object held
        // by an ancestor Provider. By default, even this listens to
        // changes in the model, and rebuilds the whole encompassing widget
        // when notified.
        //
        // By using `listen: false` below, we are disabling that
        // behavior. We are only calling a function here, and so we don't care
        // about the current value. Without `listen: false`, we'd be rebuilding
        // the whole MyHomePage whenever Counter notifies listeners.
        onPressed: () =>
            Provider.of<Counter>(context, listen: false).increment(),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),

⚠️注意:虽然我们可以使用 Consumer 来实现这个效果,不过这么实现有点浪费。因为我们让整体框架重构了一个无需重构的 widget。所以这里我们可以使用 Provider.of,并且将 listen 设置为 false。在 build 方法中使用上面的代码,当 notifyListeners 被调用的时候,并不会使 widget 被重构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值