随着 riverpod_generator 软件包的推出,使用 Riverpod 编写 Flutter 应用程序变得容易多了。
使用新的 Riverpod 语法,我们可以使用 @riverpod 注解,让 build_runner 即时生成所有providers。
我已经在前文中介绍了所有基础知识。
在本文中,我们将进一步了解在 Riverpod 2.0 中添加的 Notifier 和 AsyncNotifier 类。
以及我们还将介绍 Riverpod 2.3 中新增的 StreamNotifier 类。
这些类旨在取代 StateNotifier,并带来一些新的好处:
- 更容易执行复杂的异步初始化
- 更符合人体工程学的 API:不再需要传递 ref
- 不再需要手动声明providers(如果我们使用 Riverpod Generator)
最后,你将知道如何以最小的代价创建自定义状态类,并使用 riverpod_generator 快速生成复杂的providers。
准备好了吗?开始吧 🔥
本文假设你已经熟悉了 Riverpod。
本文介绍内容
为了让本教程更容易理解,我们将使用两个示例。
- 简单计数器
第一个示例将是一个基于 StateProvider 的简单计数器。
我们将把它转换为新的 Notifier 并学习其语法。
之后,我们将添加 Riverpod Generator,了解如何自动生成相应的 NotifierProvider。
- 认证控制器
然后,我们将学习一个更复杂的示例,其中包含一些基于 StateNotifier 的异步逻辑。
我们将把它转换为使用新的 AsyncNotifier 类,并学习异步初始化的一些细微差别。
我们还会将其转换为使用 Riverpod Generator,并生成相应的 AsyncNotifierProvider。
最后,我们将总结 Notifier 和 AsyncNotifier 的优点,以便您选择是否在应用程序中使用它们。
此外,我还将分享一些源代码,向大家展示如何将所有功能整合在一起。
让我们开始吧!👇
一个使用StateProvider的简单Counter
第一步,让我们考虑一个简单的 StateProvider 以及使用它的 CounterWidget:
// 1. declare a [StateProvider]
final counterProvider = StateProvider<int>((ref) {
return 0;
});
// 2. create a [ConsumerWidget] subclass
class CounterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 3. watch the provider and rebuild when the value changes
final counter = ref.watch(counterProvider);
return ElevatedButton(
// 4. use the value
child: Text('Value: $counter'),
// 5. change the state inside a button callback
onPressed: () => ref.read(counterProvider.notifier).state++,
);
}
}
这里没有什么花哨的东西:
- 我们可以在构建方法中观察计数器的值
- 我们可以在按钮回调中增加计数器值
正如我们所见,StateProvider 很容易声明:
final counterProvider = StateProvider<int>((ref) {
return 0;
});
这非常适合存储和更新简单的变量,如上面的计数器。
但如果你的状态需要一些验证逻辑,或者你需要表示更复杂的对象,StateProvider 就不太适用了。
对于更高级的情况,StateNotifier 是一个合适的替代品,但现在建议使用新的 Notifier 类。👇
Notifier 如何工作?
下面是我们如何声明一个基于 Notifier 类的 Counter 类。
// counter.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
class Counter extends Notifier<int> {
@override
int build() {
return 0;
}
void increment() {
state++;
}
}
有两点需要注意:
- 我们有一个返回 int(初始值)的构建方法
- 我们可以(选择性地)添加一个方法来递增状态(我们的计数器值)
如果我们想为这个类创建一个provider,可以这样做:
final counterProvider = NotifierProvider<Counter, int>(() {
return Counter();
});
或者,我们也可以使用 Counter.new 作为构造函数的拆分:
final counterProvider = NotifierProvider<Counter, int>(Counter.new);
在 Widget 中使用 NotifierProvider
事实证明,只要导入 counter.dart 文件,我们就可以在 CounterWidget 中使用 counterProvider,而无需做任何更改:
import 'counter.dart';
class CounterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 1. watch the provider and rebuild when the value changes
final counter = ref.watch(counterProvider);
return ElevatedButton(
// 2. use the value
child: Text('Value: $counter'),
// 3. change the state inside a button callback
onPressed: () => ref.read(counterProvider.notifier).state++,
);
}
}
由于我们也有一个递增方法,因此我们可以按照自己的意愿这样做:
onPressed: () => ref.read(counterProvider.notifier).increment(),
increment方法使我们的代码更具表现力。但这是可选的,因为我们仍然可以根据需要直接修改状态。
StateProvider 与 NotifierProvider
到目前为止,我们已经了解到,当我们需要修改简单变量时,StateProvider 可以很好地发挥作用。
但如果我们的状态(以及更新状态的逻辑)比较复杂,Notifier 和 NotifierProvider 是一个很好的替代方案,而且仍然易于实现:
// counter.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
class Counter extends Notifier<int> {
@override
int build() {
return 0;
}
void increment() {
state++;
}
}
final counterProvider = NotifierProvider<Counter, int>(Counter.new);
如果我们愿意,还可以自动生成提供者。👇
使用Riverpod Generator创建Notifier
下面是我们如何使用新的 @riverpod 语法声明相同的计数器类:
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'counter.g.dart';
@riverpod
class Counter extends _$Counter {
@override
int build() {
return 0;
}
void increment() {
state++;
}
}
请注意,在这种情况下,我们扩展的是 _$Counter,而不是 Notifier。
如果我们运行 dart run build_runner watch -d,就会生成包含这些代码的 counter.g.dart 文件:
/// See also [Counter].
final counterProvider = AutoDisposeNotifierProvider<Counter, int>(
Counter.new,
name: r'counterProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : $CounterHash,
);
typedef CounterRef = AutoDisposeNotifierProviderRef<int>;
abstract class _$Counter extends AutoDisposeNotifier<int> {
@override
int build();
}
需要注意的两点主要有:
- 为我们创建了一个 counterProvider
- _$Counter 扩展 AutoDisposeNotifier
AutoDisposeNotifier 在 Riverpod 中是这样定义的:
/// {@template riverpod.notifier}
abstract class AutoDisposeNotifier<State>
extends BuildlessAutoDisposeNotifier<State> {
/// {@macro riverpod.asyncnotifier.build}
@visibleForOverriding
State build();
}
我们可以看到,构建方法返回的是一个通用的 State 类型。
但这与我们的 Counter 类有何关联,Riverpod Generator 又是如何知道使用哪种类型的呢?
答案是 _$Counter 扩展了 AutoDisposeNotifier,而 state 属性也是一个 int,因为我们定义的构建方法返回的是一个 int。
一旦我们决定了使用哪种类型,如果想避免编译时出错,就必须始终如一地使用它。
在我们的 widget 类中,只要导入生成的 counter.g.dart 文件,所有代码都将继续工作:
import 'counter.g.dart';
class CounterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 1. watch the provider and rebuild when the value changes
final counter = ref.watch(counterProvider);
return ElevatedButton(
// 2. use the value
child: Text('Value: $counter'),
// 3. change the state inside a button callback
onPressed: () => ref.read(counterProvider.notifier).state++,
);
}
}
StateProvider 还是 Notifier?
我们已经介绍了一些重要的概念,所以在继续之前,让我们做一个简单的总结。
StateProvider 仍然是存储简单状态的最简单方法:
final counterProvider = StateProvider<int>((ref) {
return 0;
});
不过,我们也可以通过 Notifier 子类和 NotifierProvider 实现同样的功能:
// counter.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
class Counter extends Notifier<int> {
@override
int build() {
return 0;
}
void increment() {
state++;
}
}
final counterProvider = NotifierProvider<Counter, int>(Counter.new);
这样做更啰嗦,但也更灵活,因为我们可以在 Notifier 子类中添加具有复杂逻辑的方法(就像我们在 StateNotifier 中做的那样)。
如果我们愿意,还可以使用新的 @riverpod 语法自动生成 counterProvider:
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'counter.g.dart';
@riverpod
class Counter extends _$Counter {
@override
int build() {
return 0;
}
void increment() {
state++;
}
}
// counterProvider will be generated by build_runner
我们创建的 Counter 类是如何存储同步状态的一个简单示例。
正如我们即将看到的,我们可以使用 AsyncNotifier 创建异步状态类,并完全取代 StateNotifier 和 StateNotifierProvider。👇
使用 StateNotifier 的更复杂示例
如果你已经使用 Riverpod 有一段时间了,就会习惯于编写 StateNotifier 子类来存储一些不可变的状态,以便你的 widget 可以监听。
例如,我们可能想使用自定义按钮类登录用户:
为了实现登录逻辑,我们可以创建以下 StateNotifier 子类:
class AuthController extends StateNotifier<AsyncValue<void>> {
AuthController(this.ref)
// set the initial state (synchronously)
: super(const AsyncData(null));
final Ref ref;
Future<void> signInAnonymously() async {
// read the repository using ref
final authRepository = ref.read(authRepositoryProvider);
// set the loading state
state = const AsyncLoading();
// sign in and update the state (data or error)
state = await AsyncValue.guard(authRepository.signInAnonymously);
}
}
我们可以通过调用 AuthRepository 的 signInAnonymously 方法来登录。
当我们在通知程序中执行异步工作时,可以多次设置状态。这样,窗口小部件就能针对每种可能的状态(数据、加载和错误)重建并显示正确的用户界面。
我们还需要创建相应的 StateNotifierProvider,以便在我们的 widget 中调用观看、读取或监听:
final authControllerProvider = StateNotifierProvider<
AuthController, AsyncValue<void>>((ref) {
return AuthController(ref);
});
该provider可用于获取控制器,并在按钮回调中调用 signInAnonymously():
onPressed: () => ref.read(authControllerProvider.notifier).signInAnonymously(),
虽然这种方法没有什么问题,但 StateNotifier 不能异步初始化。
而且声明 StateNotifierProvider 的语法有点笨拙,因为它需要两个类型注解。
不过,等一下!在上一篇文章中,我们已经了解到 Riverpod Generator 可以为我们生成provider!💡
那么我们可以用它来做这样的事情吗?
@riverpod
class AuthController extends StateNotifier<AsyncValue<void>> {
...
}
如果我们尝试这样做并运行 dart run build_runner watch -d,就会出现这样的错误:
Provider classes must contain a method named `build`.
我们应该使用新的 AsyncNotifier 类。👇
AsyncNotifier 是如何工作的?
Riverpod 文档将 AsyncNotifier 定义为异步初始化的 Notifier 实现。
下面是我们如何使用它来转换 AuthController 类:
// 1. add the necessary imports
import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 2. extend [AsyncNotifier]
class AuthController extends AsyncNotifier<void> {
// 3. override the [build] method to return a [FutureOr]
@override
FutureOr<void> build() {
// 4. return a value (or do nothing if the return type is void)
}
Future<void> signInAnonymously() async {
// 5. read the repository using ref
final authRepository = ref.read(authRepositoryProvider);
// 6. set the loading state
state = const AsyncLoading();
// 7. sign in and update the state (data or error)
state = await AsyncValue.guard(authRepository.signInAnonymously);
}
}
几点看法:
- 基类是 AsyncNotifier,而不是 StateNotifier<AsyncValue>。
- 我们需要重载构建方法并返回初始值(如果返回类型为 void,则不返回任何值)。
- 在 signInAnonymously 方法中,我们使用 ref 对象读取了另一个provider,尽管我们没有明确地将 ref 声明为属性(下文将对此进行详细说明)。
还要注意 FutureOr 的用法:这是一种代表 Future 或 T 的值的类型。在我们的示例中,这很有用,因为底层类型是 void,而我们没有任何东西要返回。
与 StateNotifier 相比,AsyncNotifier 的一个优势是它允许我们异步初始化状态。详见下面的示例。
声明 AsyncNotifierProvider
在使用更新后的 AuthController 之前,我们需要声明相应的 AsyncNotifierProvider:
final authControllerProvider = AsyncNotifierProvider<AuthController, void>(() {
return AuthController();
});
或者,使用构造器拆分:
final authControllerProvider =
AsyncNotifierProvider<AuthController, void>(AuthController.new);
请注意,创建provider的函数并没有 ref 参数。
不过,在 Notifier 或 AsyncNotifier 子类中,ref 始终可以作为属性访问,因此可以轻松读取其他provider。
这与 StateNotifier 不同,在 StateNotifier 中,如果我们想使用 ref,就需要将其作为构造函数参数明确传递给它。
关于 autoDispose 的注意事项
请注意,如果您使用 autoDispose 这样声明 AsyncNotifier 和相应的 AsyncNotifierProvider,则
class AuthController extends AsyncNotifier<void> {
...
}
// note: this will produce a runtime error
final authControllerProvider =
AsyncNotifierProvider.autoDispose<AuthController, void>(AuthController.new);
然后就会出现运行时错误:
Error: Type argument 'AuthController' doesn't conform to the bound 'AutoDisposeAsyncNotifier<T>' of the type variable 'NotifierT' on 'AutoDisposeAsyncNotifierProviderBuilder.call'.
将 AsyncNotifier 与 autoDispose 结合使用的正确方法是扩展 AutoDisposeAsyncNotifier 类:
// using AutoDisposeAsyncNotifier
class AuthController extends AutoDisposeAsyncNotifier<int> {
...
}
// using AsyncNotifierProvider.autoDispose
final authControllerProvider =
AsyncNotifierProvider.autoDispose<AuthController, void>(AuthController.new);
.autoDispose 修饰符可用于在移除所有监听器时重置provider的状态。
好消息是,如果使用 Riverpod Generator,我们完全不必担心语法是否正确。👇
使用Riverpod Generator生成 AsyncNotifier
就像我们在 Notifier 中使用新的 @riverpod 语法一样,我们也可以在 AsyncNotifier 中使用新的 @riverpod 语法。
下面介绍如何转换 AuthController 以使用它:
// 1. import this
import 'package:riverpod_annotation/riverpod_annotation.dart';
// 2. declare a part file
part 'auth_controller.g.dart';
// 3. annotate
@riverpod
// 4. extend like this
class AuthController extends _$AuthController {
// 5. override the [build] method to return a [FutureOr]
@override
FutureOr<void> build() {
// 6. return a value (or do nothing if the return type is void)
}
Future<void> signInAnonymously() async {
// 7. read the repository using ref
final authRepository = ref.read(authRepositoryProvider);
// 8. set the loading state
state = const AsyncLoading();
// 9. sign in and update the state (data or error)
state = await AsyncValue.guard(authRepository.signInAnonymously);
}
}
因此,基类是 _$AuthController,并且是自动生成的。
如果我们查看生成的代码,就会发现以下内容:
/// See also [AuthController].
final authControllerProvider =
AutoDisposeAsyncNotifierProvider<AuthController, void>(
AuthController.new,
name: r'authControllerProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: $AuthControllerHash,
);
typedef AuthControllerRef = AutoDisposeAsyncNotifierProviderRef<void>;
abstract class _$AuthController extends AutoDisposeAsyncNotifier<void> {
@override
FutureOr<void> build();
}
需要注意的两点主要有:
- 为我们创建了一个 authControllerProvider
- _$AuthController 扩展了 AutoDisposeAsyncNotifier。
反过来,这个类在 Riverpod 中是这样定义的:
/// {@macro riverpod.asyncnotifier}
abstract class AutoDisposeAsyncNotifier<State>
extends BuildlessAutoDisposeAsyncNotifier<State> {
/// {@macro riverpod.asyncnotifier.build}
@visibleForOverriding
FutureOr<State> build();
}
这次,构建方法将返回一个 FutureOr。
下面是我们的 AuthController 类:
从上图可以看出,我们正在处理 void、FutureOr 和 AsyncValue。
但这些类型之间有什么关系呢?
状态属性的类型是 AsyncValue,因为构建方法的返回类型是 FutureOr。
这意味着我们可以在 signInAnonymously 方法中将状态设置为 AsyncData、AsyncLoading 或 AsyncError。
StateNotifier 还是 AsyncNotifier?
为了便于比较,下面是之前基于 StateNotifier 的实现:
class AuthController extends StateNotifier<AsyncValue<void>> {
AuthController(this.ref) : super(const AsyncData(null));
final Ref ref;
Future<void> signInAnonymously() async {
final authRepository = ref.read(authRepositoryProvider);
state = const AsyncLoading();
state = await AsyncValue.guard(authRepository.signInAnonymously);
}
}
final authControllerProvider =
StateNotifierProvider<AuthController, AsyncValue<void>>((ref) {
return AuthController(ref);
});
这是新的:
@riverpod
class AuthController extends _$AuthController {
@override
FutureOr<void> build() {
// return a value (or do nothing if the return type is void)
}
Future<void> signInAnonymously() async {
final authRepository = ref.read(authRepositoryProvider);
state = const AsyncLoading();
state = await AsyncValue.guard(authRepository.signInAnonymously);
}
}
有了 @riverpod 语法,我们无需再手动声明提供者,从而减少了代码编写量。
此外,由于 ref 是所有 Notifier 子类的可用属性,因此我们不需要将其传递给他人。
由于我们可以像以前一样监视、读取或监听 authControllerProvider,因此部件中的代码保持不变。
异步初始化示例
由于 AsyncNotifier 支持异步初始化,因此我们可以编写这样的代码:
@riverpod
class SomeOtherController extends _$SomeOtherController {
@override
// note the [Future] return type and the async keyword
Future<String> build() async {
final someString = await someFutureThatReturnsAString();
return anotherFutureThatReturnsAString(someString);
}
// other methods here
}
在这种情况下,构建方法是真正的异步方法,只有在 future 完成时才会返回。
但任何监听器部件的构建方法都需要同步返回,不能等待 future 完成:
class SomeWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// returns AsyncLoading on first load,
// rebuilds with the new value when the initialization is complete
final valueAsync = ref.watch(someOtherControllerProvider);
return valueAsync.when(...);
}
}
为了处理这个问题,控制器将发出两个状态,部件将重建两次:
- 一次是在首次加载时使用临时 AsyncLoading 值
- 初始化完成后,再次使用新的 AsyncData 值(或 AsyncError)。
另一方面,如果我们使用同步 Notifier 或 AsyncNotifier,并使用返回 FutureOr 且未标记为 async 的构建方法,初始状态将立即可用,而Widget只会在首次加载时构建一次。
示例:向 AsyncNotifier 传递参数
有时,您可能需要向 AsyncNotifier 传递额外的参数。
这可以通过在构建方法中将它们声明为命名参数或位置参数来实现:
@riverpod
class SomeOtherController extends _$SomeOtherController {
@override
// you can add named or positional parameters to the build method
Future<String> build(int someValue) async {
final someString = await someFutureThatReturnsAString(someValue);
return anotherFutureThatReturnsAString(someString);
}
// other methods here
}
然后,您只需在watch, read, 或者 listen provider时将它们作为参数传递即可:
// this provider takes a positional argument of type int
final state = ref.watch(someOtherControllerProvider(42));
向 AsyncNotifier 或任何其他provider声明和传递参数的语法都是一样的。毕竟,它们只是普通的函数参数,而 Riverpod Generator 会为我们处理好一切。
Riverpod 2.3中的新功能:StreamNotifier
随着 Riverpod Generator 2.0.0 的发布,现在可以生成返回 Stream 的provider了:
@riverpod
Stream<int> values(ValuesRef ref) {
return Stream.fromIterable([1, 2, 3]);
}
如果我们使用 Riverpod Lint,就可以将上述provider转换为 “有状态 ”变量:
这就是结果:
@riverpod
class Values extends _$Values {
@override
Stream<int> build() {
return Stream.fromIterable([1, 2, 3]);
}
}
在源码中,build_runner 会生成一个 StreamNotifier 和相应的 AutoDisposeStreamNotifierProvider。
AsyncNotifier 和 StreamNotifier 是 FutureProvider 和 StreamProvider 的变种。如果您需要监视一个 Future 或 Stream,同时还需要添加方法来执行一些数据修改,那么class variant就是您的最佳选择。
Notifier 和 AsyncNotifier:它们值得吗?
长期以来,StateNotifier 为我们提供了很好的服务,为我们提供了一个存储复杂状态的地方,以及在 widget 树之外修改状态的逻辑。
Notifier 和 AsyncNotifier 将取代 StateNotifier,并带来一些新的好处:
- 更容易执行复杂的异步初始化
- 更符合人体工程学的 API:不再需要传递 ref
- 不再需要手动声明提供者(如果我们使用 Riverpod Generator)
对于新项目来说,这些好处都是值得的,因为新的类可以帮助你用更少的代码完成更多的工作。
但如果你已有大量使用 StateNotifier 的代码,那就要看你是否(或何时)决定迁移到新语法了。
无论如何,StateNotifier 还会存在一段时间,如果你愿意,可以一次迁移一个provider。
总结
自推出以来,Riverpod 已从一个简单的状态管理解决方案发展成为一个反应式缓存和数据绑定框架。
借助 FutureProvider、StreamProvider 和 AsyncValue 等类,Riverpod 可以轻松处理异步数据。
同样,新的 Notifier、AsyncNotifier 和 StreamNotifier 类也让我们可以使用符合人体工程学的 API 轻松创建自定义状态类。
弄清provider和修饰符(autoDispose 和 family)所有组合的正确语法是 Riverpod 的一大痛点。
但有了新的 riverpod_generator 软件包,所有这些问题都迎刃而解了,因为你可以利用 build_runner,即时生成所有provider。
有了新的 riverpod_lint ,我们就能获得专门针对 Riverpod 的 lint 规则和代码助手,帮助我们正确使用语法。
翻译原文链接:codewithandrea.com/articles/fl…
欢迎大家关注我的公众号——【群英传】,专注于「Android」「Flutter」「Kotlin」 我的语雀知识库——www.yuque.com/xuyisheng