将 Notifier 和 AsyncNotifier 与新的 Flutter Riverpod Generator 结合使用
使用Riverpod编写Flutter应用程序在引入riverpod_generator包后变得更加简便。
在新的Riverpod语法中,我们使用@riverpod
注解,并让build_runner
动态生成所有的提供程序。
我已经在这篇文章中涵盖了所有的基础知识:
在这篇文章中,我们将深入学习Riverpod 2.0中新增的Notifier
和AsyncNotifier
类。
2023年3月更新:我们还将介绍新增的
StreamNotifier
类,它已经添加到Riverpod 2.3中。
这些类旨在替代StateNotifier
并带来一些新的优势:
- 更容易执行复杂的异步初始化
- 更符合人体工程学的API:不再需要在各处传递
ref
- 不再需要手动声明提供程序(如果我们使用Riverpod Generator)
最终,您将知道如何轻松创建自定义状态类,并快速生成复杂的提供程序,使用riverpod_generator。
准备好了吗?让我们开始吧!
本文假设您已经熟悉Riverpod。如果您对Riverpod还不熟悉,请阅读:Flutter Riverpod 2.0:终极指南
为了使本教程更容易理解,我们将使用两个示例。
1. 简单计数器
第一个示例将是一个基于StateProvider
的简单计数器。
我们将把它转换为新的Notifier
并学习它的语法。 接下来,我们将加入Riverpod Generator并了解如何自动生成相应的NotifierProvider
。
2. 鉴权控制器
然后,我们将研究一个更复杂的示例,其中包含一些基于StateNotifier
的异步逻辑。
我们将把它转换为使用新的AsyncNotifier
类,并学习有关异步初始化的一些细微差别。
然后,我们还将将其转换为使用Riverpod Generator并生成相应的AsyncNotifierProvider
。
最后,我们将总结Notifier
和AsyncNotifier
的优势,以便您可以决定是否要在您的应用程序中使用它们。
我还会分享一些展示所有内容如何组合在一起的源代码。
让我们开始吧!
一个简单的计数器与StateProvider
作为第一步,让我们考虑一个简单的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++,
);
}
}
这里没有什么花里胡哨的东西:
- 我们可以在
build
方法中观察计数器的值 - 我们可以在按钮回调中递增它
正如我们所看到的,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++;
}
}
有两点需要注意:
- 我们有一个
build
方法,返回一个int
(初始值) - 我们可以(可选地)添加一个方法来增加状态(我们的计数器值)
如果我们想为这个类创建一个提供者,我们可以这样做:
final counterProvider = NotifierProvider<Counter, int>(() {
return Counter();
});
另外,我们可以使用Counter.new
作为构造函数引用:
final counterProvider = NotifierProvider<Counter, int>(Counter.new);
在小部件中使用 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++,
);
}
}
由于我们还拥有一个 increment
方法,如果我们希望的话,我们可以这样做:
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);
如果我们愿意的话,我们可以自动化生成提供程序。
使用RiverpodGenerator的Notifier
以下是如何使用新的 @riverpod
语法声明相同的 Counter
类:
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
。
如果我们运行flutter pub run build_runner watch
,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();
}
正如我们所看到的,build
方法返回了一个通用的 State
类型。
但这与我们的 Counter
类有什么关系,Riverpod Generator 又是如何知道要使用哪种类型呢? build
方法的返回类型决定了 state
属性的类型。答案是 _$Counter
继承自 AutoDisposeNotifier
,而 state
属性也是一个 int
,因为我们定义了 build
方法返回 int
。
一旦我们决定使用哪种类型,如果我们想避免编译时错误,就需要一致使用它。
在我们的小部件类内部,只要我们导入生成的 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
子类,以存储一些不可变状态,供您的小部件监听。
例如,我们可能希望使用自定义按钮类登录用户: 一个按钮类,用于进行登录操作。为了实现登录逻辑,我们可以创建以下的StateNotifier
子类:
dart
class SignInButton {
// Your button class implementation here
}
class SignInNotifier extends StateNotifier {
// Implementation for sign-in logic
}
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
方法来进行匿名登录。
当我们在Notifier内进行异步工作时,可以多次设置状态。这样,小部件可以重建并显示每种可能的状态(数据、加载和错误)的正确UI。
如果您对存储库模式不熟悉,请阅读这篇文章:Flutter应用程序架构:存储库模式
如果您对
AsyncValue.guard
的语法不熟悉,请阅读这篇文章:在StateNotifier子类内使用AsyncValue.guard而不是try/catch
此外,我们还需要创建相应的StateNotifierProvider
,以便我们可以在小部件内调用watch
、read
或listen
。
final authControllerProvider = StateNotifierProvider<
AccountScreenController, AsyncValue<void>>((ref) {
return AuthController(ref);
});
您可以使用此提供程序在按钮回调中获取控制器并调用 signInAnonymously()
:
onPressed: () => ref.read(authControllerProvider.notifier).signInAnonymously(),
虽然采用这种方法没有问题,但 StateNotifier
不能进行异步初始化。
而且声明 StateNotifierProvider
的语法有点笨拙,因为它需要两个类型注解。
但请稍等!在先前的文章中,我们已经学到Riverpod Generator可以为我们生成提供程序!
那么我们可以使用它来做类似这样的事情吗?
@riverpod
class AuthController extends StateNotifier<AsyncValue<void>> {
...
}
如果我们尝试执行以下命令并运行 flutter pub run build_runner watch
,会出现如下错误:
Provider classes must contain a method named `build`.
事实证明,我们不能在 StateNotifier
中使用 @riverpod
语法。相反,我们应该使用新的 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
。> - 我们需要覆盖
build
方法,并返回初始值(如果返回类型为void
,则返回空)。 - 在
signInAnonymously
方法中,我们使用ref
对象读取了另一个提供程序,尽管我们没有明确将ref
声明为属性(下面会详细解释)。
还请注意使用 FutureOr
:这是一个表示既可以是 Future
也可以是 T
的值的类型。在我们的示例中很有用,因为底层类型是 void
,我们没有什么可返回的。
AsyncNotifier
相对于StateNotifier
的一个优势是它允许我们异步初始化状态。有关更多详细信息,请参见下面的示例(具有异步初始化)。
声明 AsyncNotifierProvider
在我们可以使用更新后的 AuthController
之前,我们需要声明相应的 AsyncNotifierProvider
:
final authControllerProvider = AsyncNotifierProvider<AuthController, void>(() {
return AuthController();
});
或者,使用构造函数的引用:
final authControllerProvider =
AsyncNotifierProvider<AuthController, void>(AuthController.new);
请注意创建提供程序的函数没有 ref
参数。
然而,ref
始终作为 Notifier
或 AsyncNotifier
子类内部的属性可访问,这使得很容易读取其他提供程序。
这与 StateNotifier
不同,对于 StateNotifier
,如果我们想要使用它,需要将 ref
显式传递为构造函数参数。
关于自动释放的注意事项
请注意,如果您像这样声明 AsyncNotifier
和相应的 AsyncNotifierProvider
,并使用 autoDispose
:
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
修饰符可用于在所有侦听器被移除时重置提供程序的状态。有关更多信息,请阅读:autoDispose 修饰符
好消息是,如果我们使用 Riverpod Generator,就无需担心正确的语法。
使用 Riverpod Generator 的 AsyncNotifier
就像我们使用 @riverpod
语法与 Notifier
一样,我们也可以在 AsyncNotifier
中执行相同的操作。
以下是如何将 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();
}
这次,build
方法返回一个 FutureOr
。
以下是我们的 AuthController
类,再次提供:
AsyncNotifier子类:如果`build`方法返回一个Future,状态将是一个AsyncValue。从上面的图表可以看出,我们处理`void`、`FutureOr
那么,这些类型之间有什么关系呢?
嗯,状态属性的类型是`AsyncValue
这意味着我们可以在`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
}
在这种情况下,build
方法是真正异步的,只有当future完成时才会返回。
但是,任何监听器小部件的build
方法需要同步返回,不能等待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
或带有build
方法的AsyncNotifier
,该方法返回FutureOr
并且未标记为async
,那么初始状态将立即可用,并且小部件只会在首次加载时进行一次重建。
示例:向AsyncNotifier传递参数
有时,您可能需要向AsyncNotifier
传递附加参数。
这可以通过在build
方法中声明它们为命名参数或位置参数来完成:
@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
}
随后,当您观看、阅读或收听提供者时,您可以简单地将它们作为参数传递:
// this provider takes a positional argument of type int
final state = ref.watch(someOtherControllerProvider(42));
在声明和传递参数给 `AsyncNotifier` 或其他提供者时,语法是相同的。毕竟,它们只是常规的函数参数,而 Riverpod Generator 会为我们处理一切。要获取更多细节,请查看我的之前文章中的创建和读取带注释的 FutureProvider。
Riverpod 2.3 中的新特性:StreamNotifier
随着Riverpod Generator 2.0.0的发布,现在可以生成一个返回 `Stream` 的提供者:
@riverpod
Stream<int> values(ValuesRef ref) {
return Stream.fromIterable([1, 2, 3]);
}
如果我们使用Riverpod Lint包,我们可以将上面的提供程序转换为一个“有状态”的变体: 将一个使用Riverpod Lint包的函数型提供程序转换为类型提供程序后,以下是结果:
使用Riverpod Lint包将提供程序从函数型转换为类型后的结果如下:
@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
,同时还要添加执行某些数据变更操作的方法,类变体是一种不错的选择。
Notifier 和 AsyncNotifier:是否值得使用?
长时间以来,StateNotifier
一直在为我们提供服务,提供了一个存储复杂状态和修改状态逻辑的地方,使其不再依赖于小部件树。
Notifier
和 AsyncNotifier
旨在取代 StateNotifier
并带来一些新的好处:
- 更容易执行复杂的异步初始化
- 更符合人体工程学的 API:不再需要传递
ref
- 不再需要手动声明提供者(如果使用 Riverpod Generator)
对于新项目来说,这些好处是值得的,因为新的类可以帮助您用更少的代码实现更多的功能。
但如果您有很多现有代码使用 StateNotifier
,则由您决定是否(或何时)迁移到新的语法。
无论如何,StateNotifier
还会存在一段时间,如果您愿意,可以逐个迁移您的提供者。
如何测试 AsyncNotifier 子类?
我们在这里没有涵盖的一个方面是如何编写 Notifier
和 AsyncNotifier
子类的单元测试。
这是一个有趣的话题,我在这篇文章中详细介绍了它:
结论
自从引入以来,Riverpod 从一个简单的状态管理解决方案演变为一种响应式缓存和数据绑定框架。
Riverpod 可以通过类似 FutureProvider
、StreamProvider
和 AsyncValue
的类轻松处理异步数据。 同样,新的 Notifier
、AsyncNotifier
和 StreamNotifier
类使得使用人性化的API轻松创建自定义状态类。
在Riverpod中,找出所有提供者和修饰符(autoDispose
和family
)的正确语法组合是一个主要的痛点。
但是有了新的 riverpod_generator 包,所有这些问题都会消失,因为您可以利用 build_runner
动态生成所有提供者。
而新的 riverpod_lint 包,则提供了Riverpod特定的Lint规则和代码辅助,帮助我们获得正确的语法。
通过我所有的 Riverpod 文章,我想为您详细介绍Riverpod的功能。