供应商与Riverpod。比较Flutter中的状态管理器

1,176 阅读7分钟

由于有这么多的选择,在为你的应用程序选择一个状态管理器时,很容易被淹没。似乎不同的状态管理解决方案发布的频率超过了人们的想象,每一个都希望提出一种独特的、更容易处理状态的方法。

在这篇文章中,我们将介绍两种不同的状态管理工具。Provider和Riverpod。我们将简要介绍每个工具,看看Riverpod提供的改进,以及为什么人们会选择它而不是Provider,然后强调Provider与Riverpod提供的解决方案的问题。

这篇文章假设你对Flutter很熟悉。由于这不是对Riverpod或Provider状态管理包的介绍,我们不会对它们的功能进行太深入的研究--只需要指出比较的内容即可。这篇文章的重点是Riverpod作为Provider的自然继承者。

什么是状态?

状态是一个小部件在建立时持有的信息,在小部件刷新时可以改变。在一个应用程序中的部件之间或内部存储和传递的某些数据或信息被称为 "状态"。

Flutter中的一切都涉及到处理和操作精确的细节,要么从检索它们,要么以某种形式向用户显示它们。您选择的处理状态的方法直接影响到应用程序的行为和安全性。

状态管理

状态管理指的是用于处理应用程序中的状态的技术或方法。状态管理技术有很多,适合各种需求。任何状态管理技术都没有万能的,你要选取符合你需求的、最适合你的。

河床(Riverpod

Riverpod是由Remi Rousselet(Provider的创造者)发布的一个状态管理包。Rousselet通过重新排列 "Provider "一词的字母得到了Riverpod这个词。

Riverpod的建立主要是为了解决Provider的缺陷(我们将在后面讨论其中的一些缺陷)。它快速且易于使用,开箱即用,是一个快速、轻量级的状态管理包。

自从正式发布以来,Riverpod已经在整个状态管理社区掀起了波澜,因为它以编译安全的方式处理状态,简单明了,但又非常强大。

在Riverpod中,你声明提供者并在任何你想利用它的地方调用它。Riverpod很容易、很简单、很快速。

请看这个用Riverpod进行状态管理的例子。首先,我们将整个应用包裹在一个ProviderScopeProviderScope ,在应用中创建的所有提供者都有作用域,并有可能在全球范围内使用任何声明的提供者。

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() => runApp(ProviderScope(child: RiverPodApp()));

接下来,声明提供者。

final favoriteProvider = ChangeNotifierProvider((ref) => new Favorites());

这里我们使用ChangeNotifierProvider 。这个提供者将始终为我们提供Favorites 类。

为了在我们的小组件内使用提供者,扩展ConsumerWidget

class RiverPodApp extends ConsumerWidget {
 const RiverPodApp({Key? key}) : super(key: key);
 @override
 Widget build(BuildContext context, ScopedReader watch) {
  return MaterialApp(
   home: Scaffold(
    appBar: AppBar(
     title: Text('My favorite fruit is ' + watch(favoriteProvider).fruit),
    ),
    body: Center(
     child: Column(
      children: [
       FruitButton('Apples'),
       FruitButton('Oranges'),
       FruitButton('Bananas'),
      ],
     ),
    ),
   ),
  );
 }
}

注意,ConsumerWidget 让我们访问ScopedReader 里面的build 方法,它提供了对提供者内容的访问。

在这个例子中,我们已经为不同的水果创建了三个按钮。当每个按钮被点击时,水果的名字会在应用栏中改变。当你启动应用程序时,应用程序栏显示:"我最喜欢的水果是未知的。"当每个水果按钮被点击时,水果的名字会改变。

这种变化是可能的,因为应用栏观察着Favorite 类中创建的变量fruit (默认情况下,它被称为 "未知")。当每个按钮被点击时,changeFruit 函数被调用,给水果变量分配一个新的值并更新小部件。

class FruitButton extends StatelessWidget {
 final String fruit;
 FruitButton(this.fruit);
 @override
 Widget build(BuildContext context) {
  return ElevatedButton(
   child: Text(fruit),
   onPressed: () {
    context.read(favoriteProvider).changeFruit(fruit);
   },
  );
 }
}
class Favorites extends ChangeNotifier {
 String fruit = 'unknown';
 void changeFruit(String newFruit) {
  fruit = newFruit;
  notifyListeners();
 }
}

为什么选择Riverpod?

下面列出了可能选择Riverpod的各种原因。

  • Riverpod在编译时是安全的
  • 它不直接依赖Flutter SDK
  • Riverpod可用于创建和执行单向数据流,其模型类是不可变的(意味着它们不会改变)。
  • Riverpod不直接依赖于widget树;它的操作类似于服务定位器。提供者是全局声明的,可以在应用程序的任何地方使用。
  • Riverpod通过ScopedReader 给予widgets对提供者的访问权,它被传递给构建方法,最后通过ConsumerWidget 类被消耗。

Riverpod解决的Provider的问题

Riverpod解决了Provider的几个缺陷。

首先,与Riverpod不同,Provider完全依赖于Flutter。因为它的部件被用来提供树下的对象或状态,它完全依赖于Flutter,导致UI代码和依赖注入的混合。

另一方面,Riverpod不依赖于widget;你可以在Riverpod中声明一个提供者,并在应用程序中的任何地方使用它,不管父widget是什么。Riverpod中的提供者被声明为全局变量,放在任何文件内。

提供者也只依靠对象类型来解决widget所要求的对象。如果你提供两个相同的类型,你只能得到一个更接近于调用的站点。然而,Riverpod支持同一类型的多个提供者,你可以随时随地使用它们。

有了Provider,如果你试图访问一个非提供的类型,你会在运行时出现一个错误。这个运行时的错误不应该是这样的,因为我们应该在编译应用程序的时候尽可能多地捕捉错误。Riverpod通过在编译应用程序的过程中捕捉错误来解决这个问题,使用户体验更加完美。

结合两个或更多的供应商会导致可怕的嵌套代码。Riverpod使用ProviderReference 来处理这个问题。提供者的依赖关系被注入并随时调用,这意味着一个提供者可以依赖另一个提供者,并通过ProviderReference 轻松调用。

这里有一个例子。

Future<void> main() async {
 WidgetsFlutterBinding.ensureInitialized();
 final sharedPreferences = await SharedPreferences.getInstance();
 runApp(MultiProvider(
  providers: [
   Provider<SharedPreferences>(create: (_) => sharedPreferences),
   ChangeNotifierProxyProvider<SharedPreferences, HomeViewModel>(
    create: (_) => HomeViewModel(sharedPreferences),
    update: (context, sharedPreferences, _) =>
      HomeViewModel(sharedPreferences),
   ),
  ],
  child: Consumer<HomeViewModel>(
   builder: (_, viewModel) => HomeView(viewModel),
  ),
 ));
}

在这个例子中,我们有HomeView ,它需要一个HomeViewModel 参数。但由于HomeViewModel 依赖于SharedPreferences ,我们需要MultiProviderProxyProvider 小工具来把所有东西放在一起。

考虑到这一点,我们可以看到,有太多的模板代码。如果所有这些提供者都在小组件之外,而不是在小组件树内,那就更好了。

相比之下,这里有一个例子,一个提供者依赖于Riverpod中的另一个提供者,没有提供者带来的嵌套问题。

final appTokenProvider = StateProvider<String>((_) => '');

final authenticateFBUser = FutureProvider<void>(
  (ref) async {
    final authFBResult = await ref.read(authProvider).login();
    ref.read(appTokenProvider).state = authFBResult.token;
  },
);

在上面的例子中,authenticateFBUser 提供者依赖于appTokenProvider ,它通过Riverpod提供的ProviderReference (ref)调用。

比较Provider和Riverpod

下面是Provider和Riverpod之间的一些比较。

  • Provider存在运行时异常,但Riverpod会处理和纠正这些异常
  • Provider不是编译安全的,而Riverpod是。
  • 在Provider中,你不能声明同一类型的多个提供者,而在Riverpod中,你可以在不覆盖其他提供者的情况下这样做。
  • 在Riverpod中,你可以声明提供者和它的类,而不需要散布应用程序的根文件
  • 在Riverpod中,提供者是全局声明的,可以在应用中的任何地方使用Consumer widget或context.read
  • 在Provider中,依赖性会导致可怕的嵌套代码,而在Riverpod中,一个提供者很容易就能使用另一个提供者来消费。ProviderReference

结论

正如我前面提到的,Riverpod是Provider的继承者,它们都是由Remi Rousselet创建的。Riverpod可以被看作是没有缺点的Provider;它纠正了Provider的许多缺陷。

然而,如前所述,每个状态管理包都有其高低之分,这完全取决于你的具体使用情况。我希望这篇文章为你提供了必要的比较,以便在这两个选项之间做出正确的决定。

The postProvider vs. Riverpod:比较Flutter中的状态管理器首次出现在LogRocket博客上。