Flutter Quick

助力您提升十倍的开发效率。了解

Flutter Quick:flutter上手provider

轻松上手provider

安装provider

定义供provider使用的数据模型

创建一个provider

读取provider中的数据

改变provider中的数据

安装provider

从provider | Flutter Package上获取最新版本进行安装。


	dependencies:
  provider: xxx
		 

定义供Provider使用的数据模型

为了在Provider中进行数据共享首先我们需要为其定义一个数据模型,为了能够订阅数据的状态通常会让这个数据模型来继承ChangeNotifier。

ChangeNotifier 是 Flutter SDK 中的一个简单的类。它用于向监听器发送通知。换言之,如果被定义为 ChangeNotifier,你可以订阅它的状态变化(这和大家所熟悉的观察者模式相类似)。

在 provider 中,ChangeNotifier 是一种能够封装应用程序状态的方法。对于特别简单的程序,你可以通过一个 ChangeNotifier 来满足全部需求。在相对复杂的应用中,由于会有多个模型,所以可能会有多个 ChangeNotifier。

示例:


class Counter with ChangeNotifier {
  int _count = 0;
 
  int get count => _count;
 
  void increment() {
    _count++;
    notifyListeners();
  }
}
		 

在上述代码中我们定义了一个计数器的数据模型,通过increment()方法来改变_count,通过get count来暴露_count的值。

创建一个Provider

在创建Provider之前我们首先来了解下provider中提供的几种不同类型的"provider":

Provider: 最基础的provider组成,接收一个值并暴露它, 无论值是什么。(通常不直接使用Provider)

ListenableProvider: 供可监听对象使用的特殊provider,ListenableProvider会监听对象,并在监听器被调用时更新依赖此对象的widgets。

ChangeNotifierProvider: 为ChangeNotifier提供的ListenableProvider规范,会在需要时自动调用ChangeNotifier.dispose。(通常用来代替ListenableProvider来使用)

ValueListenableProvider: 监听ValueListenable,并且只暴露出ValueListenable.value。

StreamProvider: 监听流,并暴露出当前的最新值。

FutureProvider: 接收一个Future,并在其进入complete状态时更新依赖它的组件。

示例:


void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => Counter(),
      child: const MyApp(),
    ),
  );
}
		 

读取Provider中的数据

读取Provider中的数据可以通过 context.watch 、 context.read、context.select 来实现:

context.watch(), 一方法使得widget能够监听泛型T上发生的改变。

context.read(),直接返回T,不会监听改变。

context.select(R cb(T value)),允许widget只监听T上的一部分(R)。

需要注意的是,context.read() 方法不会在值变化时重新widget, 并且不能在 StatelessWidget.build/State.build 方法内调用,如果要要在build中访问provider中的数据可以用 context.watch()代替。

context.xxx是provider 4.1.0中给BuildContext添加的拓展方法,等价于Provider.of(context)。

Provider.of(context)类似 context.watch();

Provider.of(context,listen: false)类似于 context.read();

示例:


class Count extends StatelessWidget {
  const Count({Key key}) : super(key: key);
 
  @override
  Widget build(BuildContext context) {
    return Text(
      ///使用 `context.watch` 可以在`Counter`改变之后重新构建Count
        '${context.watch().count}',
        key: const Key('counterState'),
        style: Theme.of(context).textTheme.headline4);
  }
}
		 

改变Provider中的数据

要改变改变Provider中的数据只需要获取到Provider中对应的模型然后调用模型的对应方法就可以了:

示例:


class MyHomePage extends StatelessWidget {
  const MyHomePage({Key key}) : super(key: key);
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('轻松上手provider'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: const [
            Text('点击按钮看看效果。'),
 
            /// 性能优化:这里将会频繁触发build的操作封装到单独的widget中来避免对整个页面频繁的build
            Count(),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        key: const Key('点我'),
 
        ///性能优化: 在这里调用`context.read`而不是`context.watch`,主要是为了避免当Counter变化时而带来的rebuild
        onPressed: () => context.read().increment(),
        tooltip: '+1',
        child: const Icon(Icons.add),
      ),
    );
  }
}
		 

在上述代码中我们调用了Counter数据模型的increment()方法,此时我们不妨回想下该方法中的具体实现:


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

在这个方法中先是将_count的值加1,然后通过notifyListeners() API来通知读取了_count值的地方进行更新(很典型的观察者模式)。

到这里呢,我们变从0到1使用了provider来实现了对数据_count的操作和共享。下面贴出示例的完成代码,小伙伴们可以将其复制到项目的main.dart中来运行下查看效果:


import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
 
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => Counter(),
      child: const MyApp(),
    ),
  );
}
 
class Counter with ChangeNotifier {
  int _count = 0;
 
  int get count => _count;
 
  void increment() {
    _count++;
    notifyListeners();
  }
}
 
class MyApp extends StatelessWidget {
  const MyApp({Key key}) : super(key: key);
 
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}
 
class MyHomePage extends StatelessWidget {
  const MyHomePage({Key key}) : super(key: key);
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('轻松上手provider'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: const [
            Text('点击按钮看看效果。'),
 
            /// 性能优化:这里将会频繁触发build的操作封装到单独的widget中来避免对整个页面频繁的build
            Count(),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        key: const Key('点我'),
 
        ///性能优化: 在这里调用`context.read`而不是`context.watch`,主要是为了避免当Counter变化时而带来的rebuild
        onPressed: () => context.read().increment(),
        tooltip: '+1',
        child: const Icon(Icons.add),
      ),
    );
  }
}
 
class Count extends StatelessWidget {
  const Count({Key key}) : super(key: key);
 
  @override
  Widget build(BuildContext context) {
    return Text(
        ///使用 `context.watch` 可以在`Counter`改变之后重新构建Count
        '${context.watch().count}',
        key: const Key('counterState'),
        style: Theme.of(context).textTheme.headline4);
  }
}
		 

上手了provider之后呢,我们接下来来进一步学习provider中一些更高级的概念和功能:

provider进阶

接下来我们来学习下provider中的一些更高级的概念和功能:

ProxyProvider

MultiProvider

Consumer

ProxyProvider

从3.0.0开始, provider中新增了一种新的provider: ProxyProvider:

ProxyProvider能够将多个来自于其他的providers的值聚合为一个新对象,并且将结果传递给Provider。

这个新对象会在其依赖的任一providers更新后被更新

下面的例子使用ProxyProvider,基于来自于另一个provider的counter值进行转化。


Widget build(BuildContext context) {
  return MultiProvider(
    providers: [
      ChangeNotifierProvider(create: (_) => Counter()),
      ProxyProvider(
        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 会将它的值传递给ChangeNotifierProvider 而非 Provider。

在provider中不允许以可能随时间改变的变量来创建对象比如:


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

上述代码是一个反面❌案例,在这种情况下,即使count变量发生变化,那么MyModel对象也不会更新。如果MyModel需要依赖一个变量来创建那么正确✅的做法是使用ProxyProvider:


int count;
 
ProxyProvider0(
  update: (_, __) => MyModel(count),
  child: ...
)
		 

因为在这个案例中MyModel的创建只依赖一个变量而不依赖自于其他的providers的值,所以我们需要使用ProxyProvider0。

注意:在使用一个provider的create/update回调时,回调函数默认是懒调用的。也就是说, 除非这个值被读取了至少一次, 否则create/update函数不会被调用。

如果你想预先计算一些逻辑, 可以通过使用lazy参数来禁用这一行为。


MyProvider(
  create: (_) => Something(),
  lazy: false,
)
		 

MultiProvider

当在大型应用中注入较多状态时, Provider 很容易变得高度耦合:


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

上述代码是一个反面❌案例,在这种情况下,正确✅的做法是使用MultiProvider:


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

以上两个例子的实际表现是一致的, MultiProvider唯一改变的就是代码书写方式。

Consumer

在provider中有另外一个非常重要的概念叫Consumer,它主要有两个作用:

提供数据读取:用来代替Provider.of(context)来获取provider中的数据;

优化性能:提供更加精细的数据刷新范围,避免无谓的刷新;

首先我们先来说说Consumer的第一个作用:

提供数据读取

接下来我们使用Consumer来对上文中的案例进行改造:


class MyHomePage extends StatelessWidget {
  const MyHomePage({Key key}) : super(key: key);
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('轻松上手provider'),
        ),
        body: Consumer(
            builder: (context, Counter counter, child) => Center(
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text('点击按钮看看效果。'),
                      Text('${counter.count}')
                    ],
                  ),
                )),
        floatingActionButton: Consumer(
            builder: (context, Counter counter, child) => FloatingActionButton(
                  key: const Key('点我'),
                  onPressed: () => counter.increment(),
                  tooltip: '+1',
                  child: const Icon(Icons.add),
                )));
  }
}
		 

在上述代码中我们使用了Consumer代替了context.read和context.watch来获取数据。另外,观察上述代码不难发现:

Consumer 使用了 Builder 模式,当它的泛型变化时就会通过 builder 重新构建。Consumer 代表了它要获取哪一个祖先中的 数据模型。

Consumer 的 builder 实际上就是一个 Function,它接收三个参数(BuildContext context, T model, Widget child):

context:build 方法传进来的 BuildContext;

T:获取到的最近一个祖先节点中的数据模型;

child:它用来构建那些与provider中数据模型无关的部分,在多次运行 builder 中,child 不会进行重建;

上述案例中,当Counter每次变化都会导致builder方法调用,从而导致其里面的widget被重新创建。

那么,接下来我们继续探索下如何使用Consumer进行性能优化。

优化性能

下面我们借助Consumer 的builder中的child 不会进行重建的原理来对上述案例进行优化:


  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('轻松上手provider'),
        ),
        body: Consumer(
          builder: (context, Counter counter, child) => Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [child, Text('${counter.count}')],
            ),
          ),
          child: Text('点击按钮看看效果。'),
        ),
        floatingActionButton: Consumer(
            builder: (context, Counter counter, child) => FloatingActionButton(
                  key: const Key('点我'),
                  onPressed: () => counter.increment(),
                  tooltip: '+1',
                  child: child,
                ),
            child: const Icon(Icons.add)));
  }