flutter Riverpod

1,889 阅读6分钟

Riverpod 是 Flutter 最喜欢的状态管理库,是 Provider 包的升级版。它修复了 Provider 包中持续存在的问题,并提高了时间和空间的复杂性。在这篇文章中,我们将讨论 Riverpod 在 Flutter 中的用途以及如何在我们的应用程序中使用它。

ValueNotifier 和 ChangeNotifier 是必需的,因为它们与 RiverPod 结合使用。首先,我们将简要讨论 Provider 及其缺点,以便我们了解对 Riverpod 的需求。

Provider 基础知识

当我们想要跨多个页面 / widget 共享数据时,我们使用状态管理技术。对此的基本解决方案之一是依赖注入。这意味着我们只需通过构造函数将依赖项注入到 widget 树中,以便后代可以直接访问它。这种方法的问题是:

  • 随着应用程序的增长,构造函数中的参数数量也会增加。
  • 如果数据更改,则无法在 Widget Tree 中进行更新。

对此的原生的解决方案是 Inherited Widget 。我们只需注入依赖项,就可以使用 BuildContext 在 Widget 树中访问它。我们在使用 Inherited Widget 时面临的一个问题是在实现 Inherited Widget 时涉及的样板代码太多。为了解决这个问题,Rémi Rousselet(Provider 的作者)引入了 Provider。

Provider 是 InheritedWidget ,但经过简化。

使用 Provider ,可以简单地创建数据 Provider 并在 Widget 树中使用它们。为了创建更新某些事件的 Provider,ChangeNotifier 和 ValueNotifier 与 Provider 结合使用。以下是我们可以使用 Provider 的三种方法:

  • 简单的非反应式 Provider - Provider< T >
  • 反应式 ChangeNotifier Provider – ChangeNotifierProvider< T >
  • 反应式 ValueNotifier Provider – ValueNotifierProvider< T >

Provider 的问题

Provider 对于中小型项目来说绰绰有余。但是,我们在使用 Provider 时会遇到一些问题:

  • 如果执行错误,将导致运行时错误。
  • 当 Widget 用于提供依赖项时,代码会耦合。
  • 不能注入两个相同类型的依赖项。
  • 需要 BuildContext 才能使用依赖项。

Flutter 中的 Riverpod 简介

为了解决 Provider 中存在的所有问题,provider 的作者自己介绍了 Riverpod。它可以用 ChangeNotifier 和 ValueNotifier 来实现。在 Provider 包之上,它还提供与 StateNotifier 的集成。在Riverpod:

  • 我们可以在编译时识别错误。
  • 现在可以注入多个相同类型的依赖项。
  • 不需要 BuildContext,因此也可以在纯 Dart 项目中使用。
  • StateNotifier 类似于 ValueNotifier,但与 ValueNotifier 不同的是,没有人可以从类外部修改 StateNotifier 的当前状态。

要将 Riverpod 添加到您的项目中,只需在 pubspec.yaml 中添加依赖项:

dependencies:
    flutter_riverpod: ^1.0.3

在 RiverPod 中,我们在全局范围内声明所有 Providers(将在前面讨论)。因此,RiverPod 会自动搜索所有 Provider 并将它们有效地注入到 Widget Tree 中。要在整个项目中启用 Riverpod 依赖项,我们需要在 ProviderScope 中覆盖我们的应用程序,如下所示:

void main() {
  runApp(
      const ProviderScope(
        child: MyApp(),
      ),
  }

现在 ProviderScope 将存储我们声明的所有 Provider 的状态。在创建我们的第一个 Provider 之前,让我们看一些常见的术语。

Flutter 中 Riverpod 的基本术语

请注意,Riverpod 是 Provider 的扩展。因此,两者的命名约定是相似的。

  • Consumer:Consumer 只是一个订阅 Provider 的 Widget。顾名思义,它使用任何 Provider 发布的事件。
  • Provider:Provider 是 Riverpod 最重要的组件。简而言之,可以将 Provider 视为共享状态的访问点。类似于我们在使用 provider 包时创建的 Providers。
  • WidgetRef:WidgetRef 是 Riverpod 提供给我们的对象,可用于与 Providers 进行交互。我们将使用 WidgetRef 在我们的 Provider 上执行所有操作(修改、监听)。

在 Riverpod 中创建 Provider

为简单起见,我将创建一个包含单个只读值的 Provider。我们在全局范围内声明所有的 Provider ,以便 Widget 树中的任何 Widget 都可以访问任何 Provider。

// Simple Provider exposes a read-only value
final textProvider = Provider((ref) => 'Hello World');

要在我们的代码中使用这个 Provider,我们可以使用 Consumer Widget 或扩展我们的 ConsumerWidget 类而不是 Stateless Widget。

class MyApp extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {...}
}

// OR

class MyApp extends StatefulWidget {
  @override
  Widget build(BuildContext context){
    return Consumer(
        builder: (_, WidgetRef ref, __) {...}
    );
  }
}

在我们的示例中,为了简单起见,我们将扩展 ConsumerWidget:

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

// 1

final textProvider = Provider((ref) => 'Hello World');

void main() {
  runApp(
    const MaterialApp(
      home: ProviderScope(
        child: TextPage(),
      ),
    ),
  );
}

// 2
class TextPage extends ConsumerWidget {
  const TextPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      body: Center(
        child: Text(
          // 3
          ref.watch(textProvider),
        ),
      ),
    );
  }
}

代码说明:

  • 我们使用文本“Hello World”创建一个简单的只读 Provider 。
  • 我们没有扩展 StatelessWidget,而是扩展了 ConsumerWidget。我们得到一个 WidgetRef 类型的额外参数,我们可以使用它与声明的所有 Provider 进行交互。
  • 使用 ref 参数,我们可以与 Provider 进行交互。我们使用 watch() 函数来观察 Provider 的当前值。
  • 我们现在已经创建了我们的第一个 Provider。但是,此 Provider 是只读的,其值永远不会更新。接下来让我们实现计数器应用程序并了解如何创建可修改的 Provider 。

使用 Riverpod 的计数器应用程序

有 3 种方法来实现计数器应用程序。正如我们所讨论的,我们需要一个可修改的 Provider ,为此我们可以使用以下三个之一:

  1. ChangeNotifier
  2. ValueNotifier
  3. StateNotifier

为简单起见,我们将在示例中使用 ChangeNotifier,因为所有人都必须熟悉它。我们需要的组件是 DATA MODEL、PROVIDER 和 UI。

因此,首先让我们创建扩展 ChangeNotifier 的数据类:

import 'package:flutter/foundation.dart';

class CounterNotifier extends ChangeNotifier {
  int value = 0;

  void increment() {
    value++;
    notifyListeners();
  }
}

现在让我们声明我们的 Provider ,它将是一个 ChangeNotifierProvider,因为我们在数据类中使用了 ChangeNotifier:

final counterProvider = ChangeNotifierProvider((ref) => CounterNotifier());

最后,让我们来实现 UI,我们在这里使用 ConsumerWidget 而不是 StatelessWidget:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_example/data/change_counter.dart';

// 1
final counterProvider = ChangeNotifierProvider((ref) => CounterNotifier());

// 2
class ChangeCounterPage extends ConsumerWidget {
  const ChangeCounterPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 3
          ref.read(counterProvider).increment();
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        // 4
        child: Text(ref.watch(counterProvider).value.toString()),
      ),
    );
  }
}

代码说明:

  • 声明您的 ChangeNotifierProvider 并将其存储在一个变量中。
  • 而不是 StatelessWidget 扩展 ConsumerWidget 以便我们获得 WidgetRef 实例,我们可以使用该实例来访问应用程序中可用的任何 Provider 。
  • 我们使用 ref.read() 来访问 Data Class 的单例实例并对其执行可用的操作。在我们的例子中,我们的数据类中唯一可用的操作是 increment() 操作。
  • 我们使用 ref.watch() 来获取提供者的值并订阅未来的事件。这样我们的 Widget 就会在值更改时重建。

结论

在这篇文章中,我们首先讨论了 Flutter 中的状态,并将 Riverpod 视为一种状态管理解决方案。我们学习了如何创建我们的 Provider 并使用它们。对于这篇文章,我们只是使用了 ChangeNotifierProvider,我们还将在以下内容中添加一系列文章来扩展这篇文章:

  • 带有 Riverpod 的 ValueNotifier
  • 带有 Riverpod 的 StateNotifier