Flutter依赖注入插件kfx_dependency_injection的使用

发布于 1周前 作者 songsunli 来自 Flutter

Flutter依赖注入插件kfx_dependency_injection的使用

kfx_dependency_injection

kfx_dependency_injection 是一个简单的依赖注入和服务定位插件,灵感来源于 .NET 服务提供者。在软件工程中,依赖注入是一种设计模式,其中对象或函数接收它所依赖的对象或函数,这样你就可以传递抽象类(在 Dart 中最接近接口的东西),让这个包在实例化时决定你的类将获得什么。

它还作为服务定位器(当你有一个抽象/接口定义,并且想要根据注册选择具体的实现)工作。

特性

  1. 允许注册临时和单例依赖项
  2. 覆盖配置错误的 lint 选项(例如,使用泛型方法但没有提供泛型类型)
  3. 注入器具有 PlatformInfo 可用,因此可以根据媒体(网页、桌面或移动设备)和主机(Windows、Linux、MacOS、Android 或 iOS)来决定注入什么。
  4. Flutter web 安全,包括 PlatformInfo
  5. 没有外部依赖项
  6. 服务可以通过实现 IMustBeTransientIMustBeSingleton 来说明它们必须是临时还是单例

使用

例如,你可以定义一个认证系统作为一个空的通用方法集(在一个抽象类中,这是目前在 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 临时

注册之间的区别在于:每当您调用 optionalrequired 或者在构造函数中注入其他内容时,单例总是返回同一类的实例,而临时注册总是返回该类的新实例(在大多数情况下,您希望它是单例)。

可选 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 当我们询问类型名称时。这就是为什么你需要使用 ashideshow 关键字在 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() 是有效的代码,但不知道应该返回哪种服务(因为 TServicedynamic)。正确的使用应该是 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'.

不合法的注册模式异常

这个抽象异常要么是 RegistryMustBeTransientExceptionRegistryMustBeSingletonException,当服务实现了 IMustBeTransient 时会被抛出,反之亦然。

示例代码


更多关于Flutter依赖注入插件kfx_dependency_injection的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于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插件进行依赖注入。首先,你创建了一个依赖注入容器并注册了依赖项。然后,你在组件中通过容器解析依赖项并使用它们。

这个示例提供了一个基本框架,你可以根据自己的需求进行扩展和修改。

回到顶部