轻松上手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
context.read
context.select
需要注意的是,context.read
context.xxx是provider 4.1.0中给BuildContext添加的拓展方法,等价于Provider.of
Provider.of
Provider.of
示例:
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
优化性能:提供更加精细的数据刷新范围,避免无谓的刷新;
首先我们先来说说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)));
}