跳到主要内容

给Provider开发者的Riverpod指南

本文是为熟悉 Provider 并希望了解Riverpod的开发者所设计的。

Riverpod 和 Provider 之间的关系

Riverpod可以说是Provider的“精神”继承者。而且“Riverpod”这个名字就是“Provider”的变位词。

Riverpod是在寻找解决Provider所面临的技术限制的方案中诞生的。 一开始Riverpod被认为是解决这一问题的Provider的下一个大版本。 但最后并没有这么做,因为这样会是一个相当大的突破性变更, 而Provider是现如今最常用的Flutter package 之一。

当然了,Riverpod和Provider从概念上来说还是差不多的。 两个package都扮演着类似的角色。两者都试图:

  • 缓存和小会一些有状态的对象
  • 在测试时提供一个模拟这些对象的方法
  • 在flutter widget中提供了一个简单的方式来监听这些对象的变化

与此同时,如果Riverpod继续成熟下去,它也可能会成为Provider。

Riverpod修复了Provider的许多基本问题,例如但不限于:

  • 显著简化了与“provider”的组合。 Riverpod提供了简单强大的工具比如 ref.watchref.listen , 而不是冗长且容易出错的 ProxyProvider
  • 允许多个“Provider”公开相同类型的值。 当你暴露一个int类型或String类型的provider时不需要自定义一个类也能完美地使用。
  • 不需要在测试中重新定义provider。 在Riverpod中默认情况下provider可以直接使用。
  • 通过提供一种替代的方法来销毁对象(autoDispose),减少对“作用域”的过度依赖。 虽然很强大,但确定一个provider的作用域相当复杂而且很难做对。

还有更多……

Riverpod唯一的缺点就是他需要变更Widget继承的类型才能使用:

  • 在Riverpod中你应该扩展 ConsumerWidget 类,而不是 StatelessWidget 类。
  • 在Riverpod中你应该扩展 ConsumerStatefulWidget 类,而不是 StatefulWidget 类。

但从大局上来看这种不便利的影响还是比较小的。而且这个限制在未来可能被移除。

所以要回答这个问题,你可能会问自己: 我要使用Provider 还是 Riverpod ?

你大概率会选择 Riverpod。 Riverpod设计得更好还能急剧简化你的逻辑。

Provider和Riverpod的差异

定义 providers

两个package最主要的区别在于如何定于“provider”。

Provider 来说,provider是widget,因此被放在widget树中,一般位于 MultiProvider 中:

class Counter extends ChangeNotifier {
...
}

void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<Counter>(create: (context) => Counter()),
],
child: MyApp(),
)
);
}

对于Riverpod来说, provider 不是 widget 而是普通的Dart对象。 类似地,provider定义在widget树之外,并声明为全局final变量。

另外,为了让Riverpod工作,应当在整个应用上方添加一个 ProviderScope widget。 下面是Riverpod的provider示例:

// Providers 现在是顶级变量
final counterProvider = ChangeNotifierProvider<Counter>((ref) => Counter());

void main() {
runApp(
// 这个 widget 可以让Riverpod在整个Flutter 项目中使用。
ProviderScope(
child: MyApp(),
),
);
}

provider的定义只是向上移动了几行。

信息

由于Riverpod的provider是普通的Dart对象,因此可以同时在Dart和Flutter使用Riverpod。 例如Riverpod也可以编写命令行应用。

读取provider: BuildContext

当使用Provider时,读取provider的一种方法是使用widget的 BuildContext.

例如,如果provider被定义为:

Provider<Model>(...);

然后使用 Provider 读取它:

class Example extends StatelessWidget {

Widget build(BuildContext context) {
Model model = context.watch<Model>();

}
}

Riverpod 的情况也一样:

final modelProvider = Provider<Model>(...);

class Example extends ConsumerWidget {

Widget build(BuildContext context, WidgetRef ref) {
Model model = ref.watch(modelProvider);

}
}

注意:

  • Riverpod的代码段展示了其扩展自 ConsumerWidget 类而不是 StatelessWidget 类。 它在build函数中添加了一个额外的参数: WidgetRef

  • 不同于 BuildContext.watch ,我们在Riverpod中使用来自ConsumerWidgetWidgetRef 来调用 WidgetRef.watch

  • Riverpod不依赖泛型类型。相反,它依赖于使用的provider定义创建的变量。

它们的使用关键词很相似。Provider和Riverpod都适用关键字“watch”来描述“当值发生变化时,这个widget应该重新构建”。

信息

Riverpod使用和Provider相同的术语来读取provider。

  • BuildContext.watch -> WidgetRef.watch
  • BuildContext.read -> WidgetRef.read

context.watchcontext.read 的规则也适用于Riverpod: 在 build 方法中使用 “watch”。在点击或其他事件中使用 “read”。

读取 provider: Consumer

Provider附带了一个名为 Consumer (以及 Consumer2 之类的变体) 的widget用来读取provider。

Consumer 对性能优化很有帮助,它允许更细粒度的widget树重新构建,在状态变更时只更新相关的widget:

因此,如果provider被定义为:

Provider<Model>(...);

Provider允许你通过 Consumer 读取provider:

Consumer<Model>(
builder: (BuildContext context, Model model, Widget? child) {

}
)

Riverpod也有这样的原则。Riverpod也有一个名为 Consumer 的相同目的的widget。

如果我们定义provider为:

final modelProvider = Provider<Model>(...);

然后使用 Consumer 我们可以这样:

Consumer(
builder: (BuildContext context, WidgetRef ref, Widget? child) {
Model model = ref.watch(modelProvider);

}
)

请注意这个 Consumer 是如何提供给我们 WidgetRef 对象的。 这和我们在前面与 ConsumerWidget 相关的内容中看到的对象相同。

组合 provider: 带有无状态对象的 ProxyProvider

当使用Provider,当组合不同的provider时官方的方法是使用 ProxyProvider widget (或者使用比如 ProxyProvider2 的变体)。

比如说,我们定义了:

class UserIdNotifier extends ChangeNotifier {
String? userId;
}

// ...

ChangeNotifierProvider<UserIdNotifier>(create: (context) => UserIdNotifier()),

接下来我们有两个选择: 我们可能结合 UserIdNotifier 来创建一个新的 “无状态(stateless)” 的provider(通常是一个重载了==方法的不可变的值), 比如:

ProxyProvider<UserIdNotifier, String>(
update: (context, userIdNotifier, _) {
return 'The user ID of the the user is ${userIdNotifier.userId}';
}
)

每当 UserIdNotifier.userId 出现变化时provider会自动返回一个新的 String 的值。

我们可以在Riverpod中做类似的事,但是语法不同。
首先,在Riverpod中 UserIdNotifier 的定义是:

class UserIdNotifier extends ChangeNotifier {
String? userId;
}

// ...

final userIdNotifierProvider = ChangeNotifierProvider<UserIdNotifier>(
(ref) => UserIdNotifier(),
),

然后我们就可以根据 userId 生成我们的 String

final labelProvider = Provider<String>((ref) {
UserIdNotifier userIdNotifier = ref.watch(userIdNotifierProvider);
return 'The user ID of the the user is ${userIdNotifier.userId}';
});

注意 ref.watch(userIdNotifierProvider) 这一行。

这行代码告诉Riverpod获取 userIdNotifierProvider 的内容, 并当值发生变化时, labelProvider 也将被重新计算。 因此,当 userId 发生变化时,我们的 labelProvider 将自动更新 String

ref.watch 这一行你应该感觉类似。 这种模式在前面的如何在widget中读取provider已经解释过了。 实际上,provider现在可以像widget一样监听其他provider。

组合provider: 带有有状态对象的ProxyProvider

在组合provider时,另一个可选的替代的用法是暴露一个有状态的对象,比如 ChangeNotifier 实例。

为此我们可以使用 ChangeNotifierProxyProvider(如 ChangeNotifierProxyProvider2 等变体)。 比如我们可能定义:

class UserIdNotifier extends ChangeNotifier {
String? userId;
}

// ...

ChangeNotifierProvider<UserIdNotifier>(create: (context) => UserIdNotifier()),

然后,我们可以定义一个基于UserIdNotifier.userId的新 ChangeNotifier。举个例子:

class UserNotifier extends ChangeNotifier {
String? _userId;

void setUserId(String? userId) {
if (userId != _userId) {
print('The user ID changed from $_userId to $userId');
_userId = userId;
}
}
}

// ...

ChangeNotifierProxyProvider<UserIdNotifier, UserNotifier>(
create: (context) => UserNotifier(),
update: (context, userIdNotifier, userNotifier) {
return userNotifier!
..setUserId(userIdNotifier.userId);
},
);

这个新的provider创建一个 UserNotifier 实例(并且永远不会重新构造),并在用户ID更改时打印一个字符串。 在Riverpod中,这个功能的实现方式不同于provider。首先,我们的 UserIdNotifier 定义为:

class UserIdNotifier extends ChangeNotifier {
String? userId;
}

// ...

final userIdNotifierProvider = ChangeNotifierProvider<UserIdNotifier>(
(ref) => UserIdNotifier(),
),

接着,与前面的 ChangeNotifierProxyProvider 等价的是:

class UserNotifier extends ChangeNotifier {}

final userNotfierProvider = ChangeNotifierProvider<UserNotifier>((ref) {
final userNotifier = UserNotifier();
ref.listen<UserIdNotifier>(
userIdNotifierProvider,
(previous, next) {
if (previous?.userId != next.userId) {
print('The user ID changed from ${previous?.userId} to ${next.userId}');
}
},
);

return userNotifier;
});

这段代码的核心是 ref.listen 这一行。 ref.listen 方法是一个允许你监听其他provider和当provider变更时执行函数的工具。

该函数的 previousnext 参数对应provider 更改前的最后一个值和更改后的新值。