Flutter服务定位与钩子插件get_it_hooks的使用
Flutter服务定位与钩子插件get_it_hooks的使用
本指南将帮助你了解如何在Flutter项目中使用get_it_hooks
插件。get_it_hooks
为flutter_hooks
提供了一组钩子,使得存储在GetIt
中的数据绑定变得非常简单。
get_it_hooks
此包提供了一组钩子,用于Remi Rousselet的flutter_hooks
包,使得存储在GetIt
中的数据绑定变得非常容易。
当我提到绑定时,我指的是一个机制,该机制会在依赖的数据发生变化时自动重建小部件。
这些钩子提供了与get_it_mixin
相同的功能。你可能会问为什么还需要钩子?原因是flutter_hooks
和get_it_mixin
都试图覆盖小部件的createElement()
函数,这意味着你不能同时使用mixin和任何钩子。
开始使用
模型类创建
首先,我们创建一个模型类,我们希望用钩子来访问它:
class Model extends ChangeNotifier {
String _country;
set country(String val) {
_country = val;
notifyListeners();
}
String get country => _country;
String _emailAddress;
set emailAddress(String val) {
_emailAddress = val;
notifyListeners();
}
String get emailAddress => _emailAddress;
final ValueNotifier<String> name;
final Model nestedModel;
Stream<String> userNameUpdates;
Future get initializationReady;
}
接下来,我们将展示如何使用get_it_hooks
访问不同的属性。要使这有效,你需要在依赖项中添加flutter_hooks
和get_it_hooks
。
数据读取
所有钩子都以use...
开头。最简单的两个是useGet()
和useGetX()
,它们从GetIt
中获取数据,就像直接使用GetIt.I<Type>()
一样。
class TestStateLessWidget extends HookWidget {
@override
Widget build(BuildContext context) {
final email = get<Model>().emailAddress;
return Column(
children: [
Text(email),
Text(useGetX((Model x) => x.country, instanceName: 'secondModell')),
],
);
}
}
如上所示,useGet()
的用法与直接使用GetIt
完全相同,包括所有参数。useGetX()
也做同样的事情,但它提供了一个选择器函数,该函数必须返回引用对象的最终值。大多数情况下,你可能只会使用useGet()
,但选择器函数可以用来处理任何在使用值之前可能需要的数据处理。
useGet()
和useGetX()
可以在小部件内部和外部多次调用,并且可以在build()
函数之外调用。因为它们不是真正的钩子,而只是方便的函数。
监视数据
以下函数将返回一个值,并且每次GetIt
中的数据更改时都会重新构建小部件。
重要:所有以下函数只能在
build()
函数内调用。此外,所有这些函数必须始终以相同的顺序在每个build
上调用,否则钩子会混乱。
假设你在GetIt
中注册了一个实现ValueListenableBuilder<String>
的对象,名为currentUserName
,并且我们希望上述小部件在它的值更改时重新构建。我们可以这样做,通过添加一个ValueListenableBuilder
:
class TestStateLessWidget1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
ValueListenableBuilder<String>(
valueListenable: GetIt.I<ValueListenable<String>>(instanceName: 'currentUser'),
builder: (context, val,_) {
return Text(val);
}
),
],
);
}
}
使用钩子,我们现在可以这样写:
class TestStateLessWidget1 extends HookWidget {
@override
Widget build(BuildContext context) {
final currentUser =
useWatch<ValueListenable<String>, String>(instanceName: 'currentUser');
return Column(
children: [
Text(currentUser)
],
);
}
}
不幸的是,我们需要提供第二个泛型参数,因为Dart无法推断返回值的类型。幸运的是,我们将在以下函数中看到有一种方法可以帮助编译器。
使用useWatchX
在实际应用中,你的业务对象更有可能不是ValueListenable
本身,而是它的一些属性可能是ValueListenables
,例如我们的Model
类中的name
属性。为了对这种属性的变化做出反应,你可以使用useWatchX()
:
class TestStateLessWidget1 extends HookWidget {
@override
Widget build(BuildContext context) {
final name = useWatchX((Model x) => x.name);
/// 如果监听器嵌套更深
final innerName = useWatchX((Model x) => x.nestedModel.name);
return Column(
children: [
Text(name),
Text(innerName),
],
);
}
}
这个小部件将在任何一个被监视的ValueListenables
发生变化时重建。
你可能会好奇为什么我没有在useWatchX()
中传递Model
作为泛型参数。原因是其签名如下:
R useWatchX<T, R>(
ValueListenable<R> Function(T x) select, {
String instanceName,
}) =>
这意味着你必须传递两个泛型类型,不仅仅是T
,还有R
。如果你在select
函数中传递了T
,编译器就能推断出R
。
使用useWatchOnly & useWatchXonly
另一种常见的模式是业务对象实现Listenable
,比如ChangeNotifier
,并且当其属性之一发生变化时会通知其监听者。由于我们只想在需要更新的值改变时重新构建小部件,useWatchOnly()
允许你定义想要观察的属性,并且只有在它真正变化时才会触发重建。
useWatchXonly()
做同样的事情,但对于嵌套的Listenables
。
class TestStateLessWidget1 extends HookWidget {
@override
Widget build(BuildContext context) {
final country = useWatchOnly((Model x) => x.country);
/// 如果被监视的属性嵌套更深
final innerEmail = useWatchXOnly((Model x) => x.nestedModel, (Model o) => o.emailAddress);
return Column(
children: [
Text(country),
Text(innerEmail),
],
);
}
}
这个小部件将在Model
对象的country
或嵌套Model
的emailAddress
发生变化时重建。如果你更新Model
的emailAddress
,它不会更新,尽管它也调用了notifyListeners
。
如果你想在Model
触发notifyListeners
时得到更新,你可以通过这个选择器方法实现:
final model = useWatchOnly((Model x) => x);
流和未来
如果你希望在模型中的流发出新值或未来完成时更新你的小部件,你可以使用useWatchStream
和useWatchFuture
。好的地方在于你不必关心取消订阅,钩子会为你处理。所以,而不是使用StreamBuilder
,你可以直接这样做:
class TestStateLessWidget1 extends HookWidget {
@override
Widget build(BuildContext context) {
final currentUser = useWatchStream((Model x) => x.userNameUpdates, 'NoUser');
final ready =
useWatchFuture((Model x) => x.initializationReady, false).data;
return Column(
children: [
if (ready != true || !currentUser.hasData) // 在发生错误时,ready 可能为null
CircularProgressIndicator()
else
Text(currentUser.data),
],
);
}
}
这些函数可以处理如果选择器函数在后续的build
调用中返回不同的流和未来的场景。在这种情况下,旧的订阅会被取消,并且新的Stream
会被订阅。更多详情请查看API文档。
事件处理器
也许你不需要更新值,而是想在Stream
发出值或ValueListenable
更新值或Future
完成时显示一个Snackbar。如果你不想使用mix-in来实现这一点,你将需要一个StatefulWidget
,在initState
中订阅一个Stream
并在State
的dispose
函数中取消订阅。
使用钩子,你可以为Streams
、ValueListenables
和Futures
注册处理器,钩子会在小部件销毁时为你清理一切。
class TestStateLessWidget1 extends StatelessWidget with GetItMixin {
@override
Widget build(BuildContext context) {
/// 注册一个处理器,用于valueListenable
useRegisterHandler((Model x) => x.name, (context, name, _) => showNameDialog(context, name));
useRegisterStreamHandler((Model x) => x.userNameUpdates, (context, name, _) => showNameDialog(context, name));
useRegisterFutureHandler((Model x) => x.initializationReady, (context, __, _) => Navigator.of(context).push(...));
return Column(
children: [
//... whatever widgets needed
],
);
}
}
例如,当你使用useWatch()
获取值时,你可以注册一个处理器,用于flutter_command
的thrownExceptions
。
在上面的例子中,你可以看到处理器函数有第三个参数,我们忽略了它。你的处理器会收到一个处置函数,该函数可以在自身内部使用以注销注册。
使用useAllReady() 和 useIsReady()
如果你已经使用了GetIt
中的同步函数,你应该知道这两个函数(否则请参阅GetIt
文档)。钩子版本返回实际状态作为bool
值,并在状态更改时触发重建。此外,你可以注册处理器,在状态为true
时调用它们。
class TestStateLessWidget1 extends HooksWidget {
@override
Widget build(BuildContext context) {
final isReady = useAllReady();
if (isReady) {
return MyMainPageContent();
} else {
return CircularProgressIndicator();
}
}
}
或者使用处理器:
class TestStateLessWidget1 extends Widget {
@override
Widget build(BuildContext context) {
useAllReady(
onReady: (context) => Navigator.of(context).pushReplacement(MainPageRoute())
);
return CircularProgressIndicator();
}
}
useIsReady<T>()
可以用相同的方式使用,以响应单个异步单例的状态。
推入一个新的GetIt作用域
使用usePushScope()
,你可以推入一个作用域,当小部件/状态被销毁时会被弹出。你可以传递一个init
函数,该函数将在作用域推入后立即调用,以及一个可选的dispose
函数,该函数将在作用域弹出前直接调用。
void usePushScope({void Function(GetIt getIt) init, void Function() dispose});
更多关于Flutter服务定位与钩子插件get_it_hooks的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter服务定位与钩子插件get_it_hooks的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,我可以为你提供一个关于如何在Flutter中使用get_it_hooks
插件进行服务定位和钩子(hooks)管理的示例代码。get_it_hooks
是一个Flutter依赖注入库get_it
的扩展,它允许你在获取依赖项时执行自定义逻辑。
首先,确保你在pubspec.yaml
文件中添加了get_it
和get_it_hooks
依赖:
dependencies:
flutter:
sdk: flutter
get_it: ^7.2.0 # 请检查最新版本
get_it_hooks: ^2.0.0 # 请检查最新版本
然后,运行flutter pub get
来安装这些依赖。
接下来,我们编写一些示例代码来展示如何使用这些库。
1. 设置依赖注入容器
首先,我们需要创建一个GetIt
实例并注册一些服务。
import 'package:get_it/get_it.dart';
import 'package:get_it_hooks/get_it_hooks.dart';
final getIt = GetIt.instance;
class MyService {
String message = "Hello, World!";
}
void setupDependencies() {
getIt.registerSingleton<MyService>(MyService());
// 添加一个钩子,在每次获取MyService实例之前执行
getIt.hookFor<MyService>().beforeGet((context) {
print("Before getting MyService instance");
});
// 添加一个钩子,在每次获取MyService实例之后执行
getIt.hookFor<MyService>().afterGet((context, instance) {
print("After getting MyService instance with message: ${instance.message}");
});
}
2. 使用服务
然后,我们可以在应用程序的其他部分使用这些服务,并观察钩子的执行。
import 'package:flutter/material.dart';
import 'your_dependency_setup_file.dart'; // 假设上面的代码保存在这个文件里
void main() {
// 设置依赖
setupDependencies();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('GetIt Hooks Example'),
),
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 获取服务并显示消息
final myService = getIt.get<MyService>();
return Text(myService.message);
}
}
3. 运行应用
运行你的Flutter应用,你应该会在控制台中看到钩子在获取MyService
实例之前和之后打印的消息。
Before getting MyService instance
After getting MyService instance with message: Hello, World!
总结
以上代码展示了如何在Flutter中使用get_it
和get_it_hooks
来设置和管理依赖项,以及如何在获取依赖项之前和之后执行自定义逻辑。通过这种方式,你可以轻松地在Flutter应用中实现依赖注入和钩子功能。