Providers
Providers是Riverpod的核心功能。使用Riverpod就是使用Riverpod的各种Provider。
什么是Provider
Provider本质上就是“备忘录方程”,只不过对其进行了封装,以提高其易用性。 也就是说,使用相同的参数调用Provider时,会返回已缓存的值(而不是重复请求)。 最常见的应用示例是使用Provider执行网络请求。 假设有这样一个方程,通过一个API调用该方程请求一条user数据:
Future<User> fetchUser() async {
final response = await http.get('https://api.example.com/user/123');
return User.fromJson(response.body);
}
这个方程需要处理一个问题,就是如果在widget内调用该方程,那我们就需要自己处理返回结果的缓存问题;并找到一个能将该结果分享出去的方法,以便所有需要该结果的widget都能够访问到。 这就是Provider的优势所在。Provider对方程进行了包装。Provider缓存了上述方程的请求结果,并允许多个widget访问同一个值:
// 与fetchUser方程等价,但缓存了请求结果。
// 多次调用userProvider返回相同的值
final userProvider = FutureProvider<User>((ref) async {
final response = await http.get('https://api.example.com/user/123');
return User.fromJson(response.body);
});
除了基本的缓存功能,Provider还添加了多种功能:
- 内置的缓存失效机制
需要强调的是,使用
Ref.watch就可以将多个缓存拼接起来,并在必要时对缓存做失效处理。 - 自动清理功能(Automatic Disposal) 不再使用时,自动释放Provider占用的资源。
- 数据绑定(Data-binding)
Provider可以完全替换掉
FutureBuilder与StreamBuilder。 - 自动处理出错 Provider可以自动捕获出错,并将出错暴露给UI。
- 支持数据仿造(Mocking support)
为方便测试或其他目的,所有的Provider都可以被仿造(mocked)。详见
Provider overring[^1^](#annotation1)。 - 离线持久化(实验功能)
- 形式转换实验功能)
Provider有六种形式
| Synchronous | Future | Stream | |
|---|---|---|---|
| Unmodifiable | Provider | FutureProvider | StreamProvider |
| Modifiable | NotifierProvider | AsyncNotifierProvider | StreamNotifierProvider |
对表格中的内容的解释如下。 Sync vs Future vs Stream
表格中的各列是Dart自带的Function类型。
int synchronous() => 0;
Future<int> future() async => 0;
Stream<int> stream() => Stream.value(0);
Unmodifiable vs Modifiable
Widget默认不可以修改Provider,而“Notifier”形式的Provider则可以被外部修改。 这类似于私有化的setter(不可修改的Provider):
// _state变量允许内部修改,不允许外部修改
var _state = 0;
int get state => _state;
对应于共有setter(可修改的Provider):
// 任何对象都可以修改state变量
var state = 0;
附记 原则上,也可以将 不可修改 vs 可修改 分别对应与
StatelessWidgetvsStatefulWidget。 这种对应方式并不非常准确,因为Provider并不是Widget,而且两者都可以保存状态。但原理是一致的:“一个对象,不可变”对应“两个对象,可变”。
创建一个Provider
Provider必须被创建为顶级声明。 也就是说,provider不可以声明在类中,也不可以声明在方程中。 创建语法取决于Provider是“可修改的”还是“不可修改的”。
不可修改方式
final name = SomeProvider.someModifier<Result>((ref) {
// 此处书写逻辑代码
});
provider 该变量稍后用于与其他provider交互。
variable 该变量必须被声明为final类型和顶级类型(也就是全局类型)。
provider通常是Provider或FutureProvider或StreamProvidertypeProvider类型取决于方程的返回值类型。例如,要创建一个Future,就需要调用FutureProvider。最常用的是
FutureProvider。
Modifier通常在Provider类型之后会有一个“modifier”。 Modifier是可选的,通常以类型安全的方式用于对Provider的行为进行微调。 当前有两种类型的modifier:
autoDispose在Provider被使用完毕之后自动清理缓存。family用以向Provider传递参数
Ref该对象用于与其他Provider交互。 所有Provider都有一个Ref对象,该对象或者以变量形式存在与Provider方程中,或者作为一个属性存在与Notifier中。
provider functionProvider的逻辑代码就放置在这里。首次调用Provider时晖调用该方程。 之后的读取操作就不会再访问该方程,而是返回缓存值
可修改方式
final name = SomeNotifierProvider.someModifier<MyNotifier, Result>(MyNotifier.new);
class MyNotifier extends SomeNotifier<Result> {
@override
Result build() {
// 此处书写代码逻辑
}
// 此处书写自定义方法
}
provider 该变量稍后用于与其他provider交互。
variable 该变量必须被声明为final类型和顶级类型(也就是全局类型)。
provider通常是NotifierProvider或AsyncNotifierProvider或StreamNotifierProvider。typeProvider类型取决于方程的返回值类型。例如,要创建一个Future,就需要调用AsyncNotifierProvider。 最常用的类型是AsyncNotifierProvider。
Modifier通常在Provider类型之后会有一个“modifier”。 Modifier是可选的,通常以类型安全的方式用于对Provider的行为进行微调。 当前有两种类型的modifier:
autoDispose在Provider被使用完毕之后自动清理缓存。family用以向Provider传递参数
Notifier constructor参数“notifier provider”是一个用来初始化“notifier”对象的方程。 该对象通常应该“constructor tear-off”(实在不知该如何准确翻译,此处保留原貌).
Notifier如果将NotifierProvider等同于StatefulWidget,那Notifier就等同于'State'类。 该类负责将修改Provider的状态的方式暴露出去。Consumer对象可以通过ref.read(yourProvider.notifier).yourMethod()的方式访问该类里面的共有方法。提示 不要将代码逻辑书写在notifier的构造器中。 因为此时
ref以及其他属性还不可用。而应把代码逻辑书写在build方法中。
Notifier type自定义notifier继承的基类应与provider及“family”中的类型匹配,例如
- NotifierProvider -> Notifier
- AsyncNotifier -> AsyncNotifier
- StreamNotifierProvider -> StreamNotifier
build所有自定义notifier都必须重写build方法。 这个方法就相当于在非notifier类型的Provider中书写代码逻辑的地方。 不要直接调用该方法。
附记 可以声明任意多个Provider。Riverpod允许对同一种类型的状态创建多个Provider。
final cityProvider = Provider((ref) => 'London'); final countryProvider = Provider((ref) => 'England');这两个Provider都是String类型。
使用Provider
Provider自身什么也不做,在这一点上与Widget类似。
就如同Widget是对UI的描述,Provider是对状态的描述。
只不过,Provider是完全无状态的,而且还可以被初始化为const——只不过这样一来在语法上就会变得有点罗嗦。
使用Provider需要一个独立对象:ProviderContainer。
在使用Provider之前,先将Flutter应用放入一个ProviderScope中:
void main() {
runApp(
ProviderScope(child:MyApp())
);
}
完成这一步之后,就需要获取一个Ref对象用于与Provider交互。
获取Ref有两种方式:
- Provider自身有一个Ref对象 该对象是Provider方程的第一个参数,或Notifier的属性。如此Provider就可以与其他Provider相互沟通。
- Widget tree需要名为
Consumers的特殊类型Widget。Consumers通过提供一个WidgetRef对象,为Widget tree与Provider tree之间架起了沟通的桥梁。
假设有一个helloWorldProvider返回一个字符串。调用示例如下:
class Example extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer(
builder:(context, ref, _) {
// 获取Provider的值
final helloWorld = ref.watch(helloWorldProvider);
// 在UI使用获取到的值
return Text(helloWorld);
}
);
}
}
1 Provider overring
在Riverpod中,所有的Provider都可以被重写,以改变其行为。该功能对测试、调试,或这为不同的场景提供不同的Provider实现——甚至 Scoping provider——都很方便。
在ProviderContainer/ProviderScope中通过override参数完成对Provider的重写。在该参数中可以指定一系列指令,以完成对特定Provider的重写。
通过自定义Provider结合overrideWithSomething,这些重写指令就可以被创建出来。
有一系列这类方法,所有的这些方法都是以overrideWith开头的,包括:
- overrideWith
- overrideWithValue
- overrideWithBuild 一个典型的重写示例如下:
void main() {
runApp(
ProviderScope(
overrides: [
// 所有的重写操作都写在这里
// 下面的代码演示如何重写一个“counter provider”以便使用不同的初始值
counterProvider.overrideWith((ref) => 42),
]
)
);
}