Flutter依赖注入插件kfx_dependency_injection的使用
Flutter依赖注入插件kfx_dependency_injection的使用
kfx_dependency_injection
kfx_dependency_injection
是一个简单的依赖注入和服务定位插件,灵感来源于 .NET 服务提供者。在软件工程中,依赖注入是一种设计模式,其中对象或函数接收它所依赖的对象或函数,这样你就可以传递抽象类(在 Dart 中最接近接口的东西),让这个包在实例化时决定你的类将获得什么。
它还作为服务定位器(当你有一个抽象/接口定义,并且想要根据注册选择具体的实现)工作。
特性
- 允许注册临时和单例依赖项
- 覆盖配置错误的 lint 选项(例如,使用泛型方法但没有提供泛型类型)
- 注入器具有
PlatformInfo
可用,因此可以根据媒体(网页、桌面或移动设备)和主机(Windows、Linux、MacOS、Android 或 iOS)来决定注入什么。 - Flutter web 安全,包括
PlatformInfo
- 没有外部依赖项
- 服务可以通过实现
IMustBeTransient
或IMustBeSingleton
来说明它们必须是临时还是单例
使用
例如,你可以定义一个认证系统作为一个空的通用方法集(在一个抽象类中,这是目前在 Dart 中最接近真实接口的东西)。然后,在另一个类中实现认证本身,比如说使用 Firebase 认证。想要使用 AWS Incognito?只需重写该抽象类并更改注册以指向它。完成,而不会打破更改。
日志也很重要,你也可以做同样的事情,也许使用 dart:developer
来做到这一点。
当你注册认证服务时,可以向其注入日志服务(此时,它是一个抽象类,不关心如何实现)。将日志注册更改为其他类型(可能是远程日志?),一切都会完美运行,而且你甚至不需要接触认证服务(因为一切都是基于接口/抽象类,这些只是具体实现的合同)
所以在 main
方法中的注册看起来像这样:
ServiceProvider.registerSingleton<IAuthenticationService>(
(optional, required, platform) => FirebaseAuthenticationService(
logService: required<ILogService>()
)
);
ServiceProvider.registerSingleton<ILogService>(
(optional, required, platform) => DartDeveloperLogService(isWeb: platform.platformMedia == PlatformMedia.web)
);
你可以调用 optional<TService>()
获取可选服务(如果没有注册,则返回 null
)或 required<TService>()
获取所需的特定实现 TService
。
请注意,注册的顺序并不重要,只要你在使用它们之前注册所有依赖项(一个好的地方是在 main
方法中,在应用启动之前)。
platformInfo
参数是一个 PlatformInfo
实例,因此你可以立即知道你正在使用的媒体类型(Flutter Web、Flutter Desktop 或 Flutter Mobile)以及主机(Android、iOS、Windows、MacOS 或 Linux)。这些信息被分离成媒体和主机,因此你可以知道你正在运行 Flutter Web 在 Android 设备上(例如,可能为了选择合适的 UI 系统(即 Material、Apple 或 Fluent))。在上面的例子中,我可以告诉我的具体日志实现我们是否在运行 Flutter Web 或原生。
现在,要获取你的认证服务,带有已定义的日志服务的注册,你只需要:
final authenticationService = ServiceProvider.required<IAuthenticationService>();
就这样。你不需要知道具体的实现,也不需要知道使用的日志类型(或者它是否存在)。
单例 vs 临时
注册之间的区别在于:每当您调用 optional
、required
或者在构造函数中注入其他内容时,单例总是返回同一类的实例,而临时注册总是返回该类的新实例(在大多数情况下,您希望它是单例)。
可选 vs 必需
这两个方法的区别在于 optional
可以在未注册指定服务时返回 null
,而 required
将抛出 ServiceNotRegisteredException
如果服务未注册。你应该使用 required
来确保在应用程序初始化期间正确注册了所有内容。
模拟
注册后,您可以使用 ServiceProvider.override<TService>((optional, required, platform) => MockClass(), key: "some key")
来覆盖它。
这在您使用某些远程 API 服务注册到您的应用程序中很有用,但在单元测试中想模拟该服务。在这种情况下,应用程序保持不变(无需更改)。在您的单元测试中,覆盖注册为一些模拟类,并且就完成了。
此覆盖可以在正常注册之前或之后进行(即:您可以在启动应用程序并注册类型之前或之后覆盖它,这没关系)。
额外信息
有一些方法用于检查类型是否已注册(isRegistered<T>()
)和允许注销(这对于释放单例实例或在单元测试中清理 ServiceProvider
管理器非常有用)。
由于 Dart 无法返回唯一的类型名称,ServiceProvider
所有的方法都接受一个键参数,该参数将用于区分类型。
例如:firebase_authentication
包含一个 User
类。很可能你也有一个名为 User
的的类在你的代码中,与那个 Firebase 实现无关。问题在于 Dart 将返回 User
当我们询问类型名称时。这就是为什么你需要使用 as
、hide
和 show
关键字在 import
期间避免类和函数名称冲突的原因。
所以,如果你有两个同名的类并且想要注册它们(因为不可能注册同一个类型的多次),你可以使用 key
参数来区分它们:
import 'some_class.dart';
import 'package:some_package:some_class.dart' as SomePackage;
ServiceProvider.registerSingleton<SomeClass>(
(optional, required, platform) => SomeClass()
);
ServiceProvider.registerSingleton<SomePackage.SomeClass>(
(optional, required, platform) => SomePackage.SomeClass(),
key: "SomePackage"
);
第二个注册必须使用 key
参数,因为 SomeClass
存在于多个位置并且已经注册过。
要检索每个版本的注册,请使用相同的键:
import 'some_class.dart';
final someClass = ServiceProvider.required<SomeClass>();
这将返回第一个注册(因为在两种情况下 key
都是 null
)。
import 'package:some_package:some_class.dart';
final someClass = ServiceProvider.required<SomeClass>(key: "SomePackage");
请注意,上述代码不再有别名,但它仍然返回正确的 SomeClass
版本,因为使用了相同的 key
参数。
异常
此包中有三种可用异常:
- ServiceAlreadyRegisteredException:当您尝试使用相同的
key
(或缺乏key
)注册类型时会抛出此异常。 - ServiceNotRegisteredException:由
required
时如果它要求未注册的服务抛出,或者在unregister
方法中如果throwsExceptionIfNotRegistered
参数设置为true
(默认为false
)抛出。 - ServiceInvalidInferenceException:Dart 默认不会警告您在期望泛型参数时没有提供泛型参数的情况,因此
ServiceProvider.optional()
是有效的代码,但不知道应该返回哪种服务(因为TService
是dynamic
)。正确的使用应该是ServiceProvider.optional<SomeTypeHere>()
。
同样,注册为 null 类型也是有效的代码:ServiceProvider.registerSingleton((sp) => null)
,但类型将是 Null
。
要对这些情况发出警告,您应该在 analysis_options.yaml
中打开 strict-inference
语言分析器设置:
analyzer:
language:
strict-inference: true
此设置将给出类似这样的警告:
ServiceProvider.optional();
The type argument(s) of the function 'optional' can't be inferred.
Use explicit type argument(s) for 'optional'.
不合法的注册模式异常
这个抽象异常要么是 RegistryMustBeTransientException
或 RegistryMustBeSingletonException
,当服务实现了 IMustBeTransient
时会被抛出,反之亦然。
示例代码
更多关于Flutter依赖注入插件kfx_dependency_injection的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter依赖注入插件kfx_dependency_injection的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter项目中使用kfx_dependency_injection
插件进行依赖注入的示例代码。kfx_dependency_injection
插件允许你以一种结构化和可维护的方式管理应用程序中的依赖关系。
1. 添加依赖
首先,你需要在你的pubspec.yaml
文件中添加kfx_dependency_injection
依赖:
dependencies:
flutter:
sdk: flutter
kfx_dependency_injection: ^x.y.z # 替换为最新版本号
然后运行flutter pub get
来安装依赖。
2. 创建依赖注入容器
接下来,你需要创建一个依赖注入容器来注册和管理依赖项。你可以在你的应用程序的入口点(通常是main.dart
)中完成这个操作。
import 'package:flutter/material.dart';
import 'package:kfx_dependency_injection/kfx_dependency_injection.dart';
void main() {
// 创建依赖注入容器
final container = DIContainer();
// 注册依赖项(示例)
container.registerSingleton<MyService>(() => MyService());
container.registerFactory<MyRepository>(() => MyRepositoryImpl());
// 将容器传递给应用程序的其他部分(示例)
runApp(MyApp(container: container));
}
class MyApp extends StatelessWidget {
final DIContainer container;
MyApp({required this.container});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomeScreen(container: container),
);
}
}
3. 定义依赖项
现在,定义你将要注入的依赖项。例如,定义一个服务和它的实现。
class MyService {
void doSomething() {
print("Service is doing something");
}
}
class MyRepository {
void fetchData() {
print("Repository is fetching data");
}
}
class MyRepositoryImpl implements MyRepository {
@override
void fetchData() {
print("MyRepositoryImpl is fetching data");
}
}
4. 使用依赖注入
在你的组件中,通过依赖注入容器获取依赖项。
import 'package:flutter/material.dart';
import 'package:kfx_dependency_injection/kfx_dependency_injection.dart';
class HomeScreen extends StatelessWidget {
final DIContainer container;
HomeScreen({required this.container});
@override
Widget build(BuildContext context) {
// 获取依赖项
final myService = container.resolve<MyService>();
final myRepository = container.resolve<MyRepository>();
return Scaffold(
appBar: AppBar(
title: Text('Home Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Press buttons to use services and repositories',
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => myService.doSomething(),
child: Text('Use MyService'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => myRepository.fetchData(),
child: Text('Use MyRepository'),
),
],
),
),
);
}
}
总结
以上代码展示了如何在Flutter项目中使用kfx_dependency_injection
插件进行依赖注入。首先,你创建了一个依赖注入容器并注册了依赖项。然后,你在组件中通过容器解析依赖项并使用它们。
这个示例提供了一个基本框架,你可以根据自己的需求进行扩展和修改。