本文由 简悦SimpRead 转码,原文地址 codewithandrea.com
概述我们如何使用Riverpod来注册监听器,并用depe...... 来初始化复杂的对象。
你是否曾经需要在应用程序启动后立即注册一个监听器?
这方面的例子包括。
- 监听来自
FirebaseMessaging包的传入消息 - 侦听来自
FirebaseDynamicLinks包的动态链接。 - 倾听来自
FirebaseAuth包的认证状态变化。
在所有这些情况下,我们的目标是。
- 注册一个流监听器来处理所有传入的事件
- 运行一些代码来修改应用程序的状态或导航到一个特定的页面。
例如,你可能已经写了这样的代码来处理所有来自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包可以在这里帮助我们,我们可以用它来。
- 初始化有一个或多个依赖关系的复杂对象(通过读取相应的提供者)。
- 保持我们的应用启动逻辑的整齐和整洁
在这一过程中,我们将了解到一些有用的类,如ProviderContainer和UncontrolledProviderScope,并为我们的开发者工具箱增加一种新的有价值的技术。🛠
准备好了吗?开始吧!
重新审视应用程序的初始化逻辑
作为一个起点,让我们考虑上面例子中的这段代码。
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
}
});
}
}
有几个注意事项。
DynamicLinksService类需要一个Ref参数,我们可以用它来访问我们可能需要的任何提供者。- 我们在构造函数中立即调用私有的`_init'方法***。
- 我们使用
ref.listen为我们的流注册一个监听器。 - 在监听器的回调中,我们可以根据需要处理
previous和next值。
上面的
previous和next值的类型是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()方法的整洁。
再一次,这就是四个步骤。
- 创建一个 "StreamProvider"。
- 创建一个带有处理所有流事件的监听器的服务类
- 为该服务类创建一个提供者
- 在
main()中用ProviderContainer读取服务类的提供者。
如果你有多个服务类,这种方法可以很好地扩展,因为你可以用一行代码初始化每个服务。
final container = ProviderContainer();
container.read(authServiceProvider);
container.read(dynamicLinksServiceProvider);
container.read(messagingServiceProvider);
runApp(UncontrolledProviderScope(
container: container,
child: const MyApp(),
));
这对那些听众来说是最有用的。
在应用程序运行时始终处于活动状态。 独立于UI,不特定于任何特定的widget
如果你愿意,你可以在你的服务类中添加一个输出的Stream或ValueListenable,这样UI层的部件就可以观察到它。如果你想显示一个警告或SnackBar,或在某个事件发生时导航到一个特定的页面,这很有用。
这就是了! 我们现在有了一个可重复的过程,以可扩展的方式注册监听器,而不需要用所有的初始化逻辑重载main方法。🚀
关于如何为接受Ref参数并使用它来访问依赖关系的服务类编写单元测试的更多信息,你可以查看我的Flutter课程。👇
新的Flutter课程现在可用
我推出了一个全新的课程,它非常深入地涵盖了自动化测试,以及其他重要的主题,如用Riverpod进行状态管理,应用程序的架构,导航,以及更多。