[Flutter翻译]Flutter Riverpod:如何在应用程序启动时注册监听器

454 阅读7分钟

本文由 简悦SimpRead 转码,原文地址 codewithandrea.com

概述我们如何使用Riverpod来注册监听器,并用depe...... 来初始化复杂的对象。

你是否曾经需要在应用程序启动后立即注册一个监听器?

这方面的例子包括。

在所有这些情况下,我们的目标是。

  • 注册一个流监听器来处理所有传入的事件
  • 运行一些代码来修改应用程序的状态导航到一个特定的页面

例如,你可能已经写了这样的代码来处理所有来自FirebaseDynamicLinks的传入链接。

void main() async {
  // Normal initialization
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  // register a listener to process incoming links
  FirebaseDynamicLinks.instance.onLink.listen((link) {
    // TODO: Handle link
  });
  // run the app
  runApp(const MyApp());
}

虽然这很有效,但如果事件处理的代码很复杂的话,它就会失去控制。

如果你需要一个以上的监听器,你的main()方法很快就会成为所有应用程序启动代码的垃圾场。

幸运的是,Riverpod包可以在这里帮助我们,我们可以用它来。

  • 初始化有一个或多个依赖关系的复杂对象(通过读取相应的提供者)。
  • 保持我们的应用启动逻辑的整齐和整洁

在这一过程中,我们将了解到一些有用的类,如ProviderContainerUncontrolledProviderScope,并为我们的开发者工具箱增加一种新的有价值的技术。🛠

准备好了吗?开始吧!

重新审视应用程序的初始化逻辑

作为一个起点,让我们考虑上面例子中的这段代码。

void main() async {
  ...
  // register a listener to process incoming links
  FirebaseDynamicLinks.instance.onLink.listen((link) {
    // TODO: Handle link
  });
  runApp(const MyApp());
}

把这个放在main方法里面并不理想,特别是当事件处理逻辑很复杂,我们需要访问额外的依赖关系时。而且我们也应该避免在这里使用FirebaseDynamicLinks.instance单子。

在我们的代码中直接访问单子有各种弊端,有更好的替代方案。欲了解更多信息,请阅读:Flutter中的单子:如何避免它们以及该怎么做

一个更好的方法是。

  • 将所有的监听器和事件处理逻辑移到一个单独的类中
  • 在应用程序启动时初始化该类

因此,让我们看看如何用Riverpod包来做,遵循4个步骤的过程。

1.创建一个StreamProvider

首先,让我们创建一个StreamProvider,让我们访问我们需要的流。

final onDynamicLinkProvider = StreamProvider<PendingDynamicLinkData>((ref) {
  // For simplicity, here we use FirebaseDynamicLinks directly.
  // On production codebases we would get the stream from a DynamicLinksRepository.
  return FirebaseDynamicLinks.instance.onLink;
});

为了简单起见,我们在提供者内部直接访问FirebaseDynamicLinks.instance。但在生产代码库中,我们可以创建一个DynamicLinksRepository,将FirebaseDynamicLinks作为一个构造参数。更多细节,请阅读我关于资源库模式的文章。

2.用事件处理代码创建一个服务类

现在我们有了onDynamicLinkProvider,我们可以创建一个使用它的服务类。

class DynamicLinksService {
  // 1. Pass a Ref argument to the constructor
  DynamicLinksService(this.ref) {
    // 2. Call _init as soon as the object is created
    _init();
  }
  final Ref ref;

  void _init() {
    // 3. listen to the StreamProvider
    ref.listen<AsyncValue<PendingDynamicLinkData>>(onDynamicLinkProvider,
        (previous, next) {
      // 4. Implement the event handling code
      final linkData = next.value;
      if (linkData != null) {
        debugPrint(linkData.toString());
        // TODO: Handle linkData
      }
    });
  }
}

有几个注意事项。

  1. DynamicLinksService类需要一个Ref参数,我们可以用它来访问我们可能需要的任何提供者。
  2. 我们在构造函数中立即调用私有的`_init'方法***。
  3. 我们使用ref.listen为我们的流注册一个监听器。
  4. 在监听器的回调中,我们可以根据需要处理previousnext值。

上面的previousnext值的类型是AsyncValue<PendingDynamicLinkData>,因为一个Stream<T>总是给我们提供AsyncValue<T>类型的值。如果你不熟悉AsyncValue,请阅读: Flutter Riverpod Tip: 使用AsyncValue而不是FutureBuilder或StreamBuilder

我还应该指出的是。

  • _init方法是私有的,而你添加到这个类的任何其他方法也应该是私有的。这样一来,启动监听器的唯一**方式就是创建一个DynamicLinksService的实例。
  • 我没有包括事件处理代码,因为这是特定的应用。如果你需要访问这个类中的任何其他依赖,你可以调用ref.read(someProvider).someMethod()

3.为服务类创建一个提供者

一旦我们有了DynamicLinksService类,我们就可以创建一个提供者,用来访问它。

final dynamicLinksServiceProvider = Provider<DynamicLinksService>((ref) {
  return DynamicLinksService(ref);
});

这是非常简单的,因为我们只需要将ref参数传递给构造函数。

但是如果我们现在启动应用程序,DynamicLinksService里面的代码将不会运行,因为dynamicLinksServiceProvider只会在我们第一次读取它的时候创建它(Riverpod提供者是懒于加载的),而且没有小部件或其他类在使用它。

换句话说:如果我们想使用DynamicLinksService,我们需要在main()方法里面初始化它。

4.用ProviderContainer读取服务类提供者

下面是我们的main()方法,再一次。

void main() async {
  // Normal initialization
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  // TODO: How to initialize our DynamicLinksService?
  // run the app
  runApp(ProviderScope(
    child: const MyApp(),
  ));
}

注意,我们只能用Ref对象创建DynamicLinksService,而main()方法并没有。🧐

为了解决这个鸡生蛋蛋生鸡的问题🐣,我们需要使用ProviderContainer。方法是这样的。

void main() async {
  // Normal initialization
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  // 1. Create a ProviderContainer
  final container = ProviderContainer();
  // 2. Use it to read the provider 
  container.read(dynamicLinksServiceProvider);
  // 3. Pass the container to an UncontrolledProviderScope and run the app
  runApp(UncontrolledProviderScope(
    container: container,
    child: const MyApp(),
  ));
}

就这样! 我们现在可以用一个ProviderContainer来读取dynamicLinksServiceProvider,然后作为参数传递给UncontrolledProviderScope

由于提供者将创建DynamicLinksService,我们的监听器现在将被注册,并在应用程序启动时处理所有传入的事件! 🏁

顺便说一下,注意对container.read()的调用会返回DynamicLinksService本身。

final dynamicLinksService = container.read(dynamicLinksServiceProvider);

但在这种情况下,我们可以忽略返回值,因为我们不需要它。此外,我们不能对它调用任何方法,因为唯一的公共方法是构造函数(启动监听器)。

ProviderContainer和UncontrolledProviderScope如何工作?

但什么是 "ProviderContainer"?Riverpod文档将其定义为。

一个存储提供者状态的对象,并允许覆盖一个特定提供者的行为。

它还这样说。

如果你使用Flutter,你不需要关心这个对象(在测试之外),因为它是由ProviderScope为你隐式创建的。

这条规则的例外情况是,如果我们需要在main()方法内创建一个接受Ref参数的对象。在这种情况下,明确地创建一个ProviderContainer给了我们一个 "逃生舱口",让我们访问/初始化DynamicLinksService

如果你在ProviderScope中有任何提供者覆盖,你现在可以把它们移到ProviderContainer中。

final container = ProviderContainer(
  overrides: [], // list your overrides here
);

结语

现在我们已经知道了如何在应用程序启动时注册一个监听器,同时保持我们的main()方法的整洁。

再一次,这就是四个步骤。

  1. 创建一个 "StreamProvider"。
  2. 创建一个带有处理所有流事件的监听器的服务类
  3. 为该服务类创建一个提供者
  4. main()中用ProviderContainer读取服务类的提供者。

如果你有多个服务类,这种方法可以很好地扩展,因为你可以用一行代码初始化每个服务。

final container = ProviderContainer();
container.read(authServiceProvider);
container.read(dynamicLinksServiceProvider);
container.read(messagingServiceProvider);
runApp(UncontrolledProviderScope(
  container: container,
  child: const MyApp(),
));

这对那些听众来说是最有用的。

在应用程序运行时始终处于活动状态独立于UI,不特定于任何特定的widget

如果你愿意,你可以在你的服务类中添加一个输出的StreamValueListenable,这样UI层的部件就可以观察到它。如果你想显示一个警告或SnackBar,或在某个事件发生时导航到一个特定的页面,这很有用。

这就是了! 我们现在有了一个可重复的过程,以可扩展的方式注册监听器,而不需要用所有的初始化逻辑重载main方法。🚀

关于如何为接受Ref参数并使用它来访问依赖关系的服务类编写单元测试的更多信息,你可以查看我的Flutter课程。👇

新的Flutter课程现在可用

我推出了一个全新的课程,它非常深入地涵盖了自动化测试,以及其他重要的主题,如用Riverpod进行状态管理,应用程序的架构,导航,以及更多。


www.deepl.com 翻译