Flutter依赖注入与MVVM架构插件injectable_mvvm的使用

Flutter依赖注入与MVVM架构插件injectable_mvvm的使用


Injectable Mvvm

MIT License Stars Pub Version Buy Me A Coffee


目录


安装 #

dependencies:
  # 添加injectable_mvvm到你的依赖项
  injectable_mvvm:
  # 添加get_it
  get_it:

dev_dependencies:
  # 添加生成器到你的开发依赖项
  injectable_mvvm_generator:
  # 如果尚未添加,则添加构建运行器
  build_runner:

设置 #

  1. 创建一个新的dart文件并定义一个全局变量作为你的GetIt实例。
  2. 定义一个顶级函数(我们称之为configureDependencies),然后用@FmvvmInit注解它。
  3. 导入生成的dart文件。该文件将遵循带有@FmvvmInit注解的函数名称,例如file_name.config.dart
  4. 在configure函数内部调用生成的扩展函数getIt.init()或自定义初始化器名称。

注意:此示例适用于版本2+。

import '<FILE_NAME>.config.dart';

final getIt = GetIt.instance;

[@FmvvmInit](/user/FmvvmInit)(
  initializerName: 'init', // 默认
  preferRelativeImports: true, // 默认
  asExtension: true, // 默认
)
void configureDependencies() => getIt.init();

// 你可以在@fmvvmInit中使用generateForDir属性告诉injectable处理哪些目录
// 下面的例子将只处理test文件夹内的文件
import '<FILE_NAME>.config.dart';

[@FmvvmInit](/user/FmvvmInit)(generateForDir: ['test'])
void configureDependencies() => getIt.init();
  1. 在主函数中调用configureDependencies(),在运行App之前执行。
void main() {
  configureDependencies();
  runApp(MyApp());
}

注册工厂 #

现在只需使用@injectable注解你的注入类,并让生成器完成工作。

[@injectable](/user/injectable)
class ServiceA {}

[@injectable](/user/injectable)
class ServiceB {
  ServiceB(ServiceA serviceA);
}
运行生成器

使用[watch]标志来监视文件系统中的更改,并根据需要重新构建。

flutter packages pub run build_runner watch

如果你只想运行一次生成器并退出,请使用:

flutter packages pub run build_runner build
生成文件内部

Injectable将为你生成所需的注册函数。

import 'package:get_it/get_it.dart' as _i1;

extension GetItInjectableX on _i1.GetIt {
  /// 初始化GetIt中的主要作用域依赖项
  Future<_i1.GetIt> init({
    String? environment,
    _i2.EnvironmentFilter? environmentFilter,
  }) async {
    final gh = _i2.GetItHelper(
      this,
      environment,
      environmentFilter,
    );
    gh.factory<ServiceA>(() => ServiceA());
    gh.factory<ServiceB>(ServiceA(getIt<ServiceA>()));
    return this;
  }
}

注册视图模型 #

现在只需使用@viewModel@singletonViewModel@lazySingletonViewModel注解你的视图模型类,并让生成器完成工作。

[@singletonViewModel](/user/singletonViewModel)
class DemoViewModel extends ANavigableViewModel {
  String _text = '';
  String get text => _text;
  set text(String text) {
    _text = text;
    notifyListeners();
  }

  DemoViewModel();
}

并在页面中继承BasePage以便从UI部分访问bindingContext或你的视图模型。

class DemoViewPage extends BasePage<DemoViewModel> {
  DemoViewPage({super.key}) : super();

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            TextField(
              onChanged: (text) {
                bindingContext.text = text;
              },
              decoration: InputDecoration(labelText: 'Enter your text'),
            ),
            SizedBox(height: 16.0),
            Text(
              'Entered Text:',
              style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 8.0),
            Text(
              bindingContext.text,
              style: TextStyle(fontSize: 16.0),
            ),
          ],
        ),
      ),
    );
  }
}

注册单例 #

使用@singleton@lazySingleton注解你的单例类。

[@singleton](/user/singleton) // 或 [@lazySingleton](/user/lazySingleton)
class ApiProvider {}

或者使用构造函数版本通过传递signalsReady来注册单例或懒加载单例。

[@Singleton](/user/Singleton)(signalsReady: true)
class Model {}

[@LazySingleton](/user/LazySingleton)()
class Model {}

处置单例 #

GetIt提供了一种方法来处置单例和懒加载单例实例,通过将dispose回调传递给注册函数。injectable_mvvm工作在静态领域,这意味着无法将实例方法传递给注解;幸运的是,injectable_mvvm提供了两种简单的方式来处理实例处置。

  1. 使用@disposeMethod注解你的单例类中的实例方法。
[@singleton](/user/singleton) // 或 lazySingleton
class DataSource {

  [@disposeMethod](/user/disposeMethod)
  void dispose(){
    // 释放实例的逻辑
  }
}
  1. 将dispose函数引用传递给@Singleton@LazySingleton注解。
[@Singleton](/user/Singleton)(dispose: disposeDataSource)
class DataSource {

  void dispose() {
    // 释放实例的逻辑
  }
}
/// dispose函数签名必须匹配Function(T instance)
FutureOr disposeDataSource(DataSource instance){
   instance.dispose();
}

FactoryMethod和PostConstruct注解 #

顾名思义,@FactoryMethod注解用于告诉injectable_mvvm使用哪个方法来创建依赖项,这包括命名构造函数、工厂构造函数和静态创建方法。

[@injectable](/user/injectable)
class MyRepository {
  [@factoryMethod](/user/factoryMethod)
  MyRepository.from(Service s);
}

当构建MyRepository时,将使用名为"from"的构造函数。

factory<MyRepository>(MyRepository.from(getIt<Service>()))

或者使用@factoryMethod注解抽象类中的静态创建函数或工厂方法。

[@injectable](/user/injectable)
abstract class Service {
  [@factoryMethod](/user/factoryMethod)
  static ServiceImpl2 create(ApiClient client) => ServiceImpl2(client);

  [@factoryMethod](/user/factoryMethod)
  factory Service.from() => ServiceImpl();
}

另一方面,@PostConstruct注解用于同步或异步初始化构建的依赖项,这仅包括公共成员方法。

[@Injectable](/user/Injectable)()
class SomeController  {
  SomeController(Service service);

  [@PostConstruct](/user/PostConstruct)()
  void init() {
   //...初始化代码
  }
}

这些注解都有一个可选的布尔标志preResolve。如果创建或初始化方法返回一个未来并且preResolve为true,则将在依赖项注册到GetIt之前预解析(await)该未来,否则将其注册为异步依赖项。


注册异步注入对象 #

如果我们要使实例创建异步,我们需要静态初始化方法,因为构造函数不能是异步的。

class ApiClient {
  static Future<ApiClient> create(Deps ...) async {
    ....
    return apiClient;
  }
}

现在只需使用@injectable注解你的类,并使用@factoryMethod注解告诉injectable_mvvm使用静态初始化方法作为工厂方法。

[@injectable](/user/injectable) // 或 lazy/singleton
class ApiClient {
[@factoryMethod](/user/factoryMethod)
  static Future<ApiClient> create(Deps ...) async {
    ....
    return apiClient;
  }
}

injectable_mvvm将自动将其注册为异步工厂,因为返回类型是一个Future。

生成的代码:

factoryAsync<ApiClient>(() => ApiClient.create());

使用注册模块(用于第三方依赖项) #

只需将实例包装在一个future中,即可完成。

[@module](/user/module)
abstract class RegisterModule {
  Future<SharedPreferences> get prefs => SharedPreferences.getInstance();
}

别忘了在解析异步注入对象时调用getAsync<T>()而不是get<T>()。


预解析futures #

如果你想预先等待future并注册其解析值,请使用@preResolve注解你的异步依赖项。

[@module](/user/module)
abstract class RegisterModule {
  [@preResolve](/user/preResolve)
  Future<SharedPreferences> get prefs => SharedPreferences.getInstance();
}

它也适用于@factoryMethod@postConstruct注解。

[@Injectable](/user/Injectable)()
class AsyncService  {
  AsyncService(Service service);

  // [@preResolve](/user/preResolve) -> 这也可以工作
  [@FactoryMethod](/user/FactoryMethod)(preResolve: true)
  static Future<AsyncService> create([@factoryParam](/user/factoryParam) String? param) =>
     Future.value(AsyncService(Service.from(param));
}

生成的代码:

import 'package:get_it/get_it.dart' as _i1;

extension GetItInjectableX on _i1.GetIt {
  /// 初始化GetIt中的主要作用域依赖项
  Future<_i1.GetIt> init({
    String? environment,
    _i2.EnvironmentFilter? environmentFilter,
  }) async {
    final gh = _i2.GetItHelper(
      this,
      environment,
      environmentFilter,
    );
    final registerModule = _$RegisterModule();
    final sharedPreferences = await registerModule.prefs;
    gh.factory<SharedPreferences>(() => sharedPreferences);
    return this;
  }
}

如上所示,这将使你的init函数变为async,所以一定要await它。


向工厂传递参数 #

如果要处理你拥有的类,只需使用@factoryParam注解你的变化构造函数参数,最多可以有两个参数!

[@injectable](/user/injectable)
class BackendService {
  BackendService([@factoryParam](/user/factoryParam) String url);
}

生成的代码:

factoryParam<BackendService, String, dynamic>(
   (url, _) => BackendService(url),
 );

使用注册模块(用于第三方依赖项)

如果你声明模块成员为方法而不是简单的访问器,injectable_mvvm会将其视为工厂方法,这意味着它会注入其参数,就像普通构造函数一样。

[@module](/user/module)
abstract class RegisterModule {
  BackendService getService(ApiClient client, [@factoryParam](/user/factoryParam) String url) => BackendService(client, url);
}

生成的代码:

factoryParam<BackendService, String, dynamic>(
     (url, _) => registerModule.getService(g<ApiClient>(), url));

绑定抽象类到实现类 #

使用@Injectable(as:…)中的’as’属性传递由已注册依赖项实现的抽象类型。

[@Injectable](/user/Injectable)(as: Service)
class ServiceImpl implements Service {}

// 或者
[@Singleton](/user/Singleton)(as: Service)
class ServiceImpl implements Service {}

// 或者
[@LazySingleton](/user/LazySingleton)(as: Service)
class ServiceImpl implements Service {}

生成的代码:

factory<Service>(() => ServiceImpl())

绑定抽象类到多个实现

由于我们不能使用类型绑定来注册多个实现,所以我们必须使用名称(标签)来注册我们的实例或在不同环境中注册。稍后我们将讨论这一点。

[@Named](/user/Named)("impl1")
[@Injectable](/user/Injectable)(as: Service)
class ServiceImpl implements Service {}

[@Named](/user/Named)("impl2")
[@Injectable](/user/Injectable)(as: Service)
class ServiceImp2 implements Service {}

接下来,在构造函数中注解注入的实例,并传递所需实现的名称。

[@injectable](/user/injectable)
class MyRepo {
   final Service service;
    MyRepo([@Named](/user/Named)('impl1') this.service)
}

生成的代码:

factory<Service>(() => ServiceImpl1(), instanceName: 'impl1')
factory<Service>(() => ServiceImpl2(), instanceName: 'impl2')

factory<MyRepo>(() => MyRepo(getIt('impl1'))

自动标记

使用小写的@named注解自动将实现类名称分配给实例名称。然后使用@Named.from(Type)注解从类型中提取名称。

@named
[@Injectable](/user/Injectable)(as: Service)
 class ServiceImpl1 implements Service {}

[@injectable](/user/injectable)
class MyRepo {
   final Service service;
    MyRepo([@Named](/user/Named).from(ServiceImpl1) this.service)
}

生成的代码:

factory<Service>(() => ServiceImpl1(), instanceName: 'ServiceImpl1')
factory<MyRepo>(() => MyRepo(getIt('ServiceImpl1'))

在不同环境下注册 #

可以通过使用@Environment(‘name’)注解来在不同的环境中注册不同的依赖项。在下面的例子中,只有当我们传递环境名称到$init(environment: ‘dev’)时,ServiceA才会被注册。

[@Environment](/user/Environment)("dev")
[@injectable](/user/injectable)
class ServiceA {}

你也可以通过分配const构造函数Environment("")到一个全局常量变量来创建自己的环境注解。

const dev = Environment('dev');
// 然后使用它来注解你的类
@dev
[@injectable](/user/injectable)
class ServiceA {}

你可以为同一个类分配多个环境名称。

@test
@dev
[@injectable](/user/injectable)
class ServiceA {}

或者使用injectable_mvvm中的env属性来分配环境名称给依赖项。

[@Injectable](/user/Injectable)(as: Service, env: [Environment.dev, Environment.test])
class RealServiceImpl implements Service {}
``

现在将环境传递给init函数将创建一个简单的环境过滤器,它只会验证没有环境或其中一个环境与给定环境匹配的依赖项。你还可以传递你自己的EnvironmentFilter来决定基于依赖项的环境键应该注册哪些依赖项,或者使用其中的一个已发布的过滤器:

- NoEnvOrContainsAll
- NoEnvOrContainsAny
- SimpleEnvironmentFilter

---

#### 注册第三方类型 <a href="#registering-third-party-types" class="hash-link">#</a>

为了注册第三方类型,创建一个抽象类并使用[@module](/user/module)注解,然后按以下方式添加你的第三方类型作为属性访问器或方法:

```dart
[@module](/user/module)
abstract class RegisterModule {
  [@singleton](/user/singleton)
  ThirdPartyType get thirdPartyType;

  [@prod](/user/prod)
  [@Injectable](/user/Injectable)(as: ThirdPartyAbstract)
  ThirdPartyImpl get thirdPartyType;
}

提供自定义初始化器 #

在某些情况下,你需要注册异步实例、单例实例或具有自定义初始化器的实例,这有点难于injectable_mvvm自行确定,因此你需要告诉injectable_mvvm如何初始化它们:

[@module](/user/module)
abstract class RegisterModule {
 // 你可以像这样注册命名的预加载类型
  [@Named](/user/Named)("BaseUrl")
  String get baseUrl => 'My base url';

  // url将被注入
  [@lazySingleton](/user/lazySingleton)
  Dio dio([@Named](/user/Named)('BaseUrl') String url) => Dio(BaseOptions(baseUrl: url));

  // 对于异步获取的实例,同样的事情也适用
  // 你所需要做的就是将你的实例包装在一个future中,并告诉injectable_mvvm如何初始化它
  [@preResolve](/user/preResolve) // 如果你需要预解析该值
  Future<SharedPreferences> get prefs => SharedPreferences.getInstance();
  // 确保在运行App之前等待你的configure函数
}

如果你遇到更奇怪的情况,你总可以在configure函数中手动注册它们。


自动注册 #

与其注解每个注入类,你可以使用基于约定的配置来自动注册你的注入类,特别是如果你遵循一致的命名约定。

例如,你可以告诉生成器自动注册任何以Service、Repository或Bloc结尾的类,使用简单的正则表达式模式:

class_name_pattern: 'Service$|Repository$|Bloc$'

要使用自动注册,创建一个名为build.yaml的文件,并添加:

targets:
  $default:
    builders:
      injectable_mvvm_generator:injectable_builder:
        options:
          auto_register: true
          # 自动注册任何匹配给定模式的类名
          class_name_pattern:
            "Service$|Repository$|Bloc$"
            # 自动注册任何匹配给定模式的文件名
          file_name_pattern: "_service$|_repository$|_bloc$"

手动顺序 #

默认情况下,injectable_mvvm尝试根据依赖关系重新排序依赖项,即如果A依赖于B,那么B将首先注册。

你可以通过给特定依赖项指定负数来手动决定其注册顺序,以使其在其他所有依赖项之前注册,或者通过指定正数来使其在其他所有依赖项之后注册。

注意:所有依赖项的默认顺序为0。

你可以使用@Order(number)注解或在@Injectable和subs中使用order属性来指定自定义顺序。

// [@Order](/user/Order)(-1) 这也可以工作
[@Injectable](/user/Injectable)(order: -1)
class Service{}

使用范围 #

GetIt v5.0引入了范围支持,允许在不同的范围内注册相关的依赖项,以便它们仅在需要时初始化并在不再使用时进行清理。

要使用GetIt的范围功能,只需使用@Scope(‘scope-name’)注解那些打算在不同范围内注册的依赖项,或者在@Injectable或其子类中传递范围名称。

带有范围名称的依赖项将生成在与主范围依赖项不同的初始化方法中。

// [@Scope](/user/Scope)('auth') 这也可以工作
[@Injectable](/user/Injectable)(scope: 'auth')
class AuthController{}

当你准备好使用auth-scope时,调用生成的范围初始化方法或扩展。

// 使用扩展
getIt.initAuthScope();
// 使用方法
initAuthScope(getIt);

// 如果范围初始化方法有预解析的依赖项,它将返回一个future,因此确保你等待它
await getIt.initAuthScope();

包含微包和外部模块 #

微包是可以依赖和使用的子包。标注为微包的包将生成一个MicroPackageModule而不是初始化方法,并且这些模块的初始化将由根包的初始化方法自动完成。

所以你只需要使用名为@FmvvmInit.microPackage()的命名构造函数来注解包作为微包。

// [@microPackageInit](/user/microPackageInit) => 短构造函数
[@FmvvmInit](/user/FmvvmInit).microPackage()
initMicroPackage(){} // 不会被调用但需要代码生成

生成的代码:

class AwesomePackageModule extends MicroPackageModule {
  [@override](/user/override)
   FutureOr<void> init(_i1.GetItHelper gh) {
     gh.factory<Dep>(() => Dep());
     gh.factory<Calculator>(() => Calculator(gh<Dep>()));
  }
}
``

默认情况下,injectable_mvvm将自动包含项目目录中的所有MicroPackageModules,除非在[@FmvvmInit](/user/FmvvmInit)中设置了includeMicroPackages为false。

```dart
[@FmvvmInit](/user/FmvvmInit)(includeMicroPackages: false)
``

也可以通过传递给@fmvvmInit的externalPackageModules属性手动包含微本地或外部模块,以便与其余本地依赖一起初始化。

注意:
分配给externalPackageModulesBefore的模块将在根依赖项之前初始化;
分配给externalPackageModulesAfter的模块将在根依赖项之后初始化。

```dart
[@FmvvmInit](/user/FmvvmInit)(
  externalPackageModulesBefore: [
  ExternalModule(AwesomePackageModule),
  ExternalModule(ThirdPartyMicroModule),
 ],
 externalPackageModulesAfter: [
  ExternalModule(CoolPackageModule),
 ],
)
void configureDependencies() {}
``

初始化模块内部的范围

外部模块可以初始化在特定的范围内,只需将范围分配给ExternalModule。

```dart
[@FmvvmInit](/user/FmvvmInit)(
  externalPackageModulesBefore: [
    ExternalModule(AwesomePackageModule, scope: 'awesome'),
  ],
 )
void configureDependencies() {}
``

然后将生成一个范围初始化方法/扩展。

```dart
await getIt.initAwesomeScope()
``

---

#### 生成问题? <a href="#problems-with-the-generation" class="hash-link">#</a>

确保在运行生成器之前始终保存你的文件。如果不起作用,你可以总是尝试清理和重建。

```terminal
flutter packages pub run build_runner clean

支持库 #

  • 你可以通过在Github上点赞并在pub上打分来支持库,也可以报告你遇到的任何错误。
  • 如果你有任何建议或认为某些东西可以更好地实现,请打开一个issue并与我们讨论。

示例代码

import 'package:example/features/demo_view_model/presentation/demo_page.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:injectable_mvvm/injectable_mvvm.dart';
import 'package:example/injector/injector.config.dart';

GetIt _ioc = GetIt.instance;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await _ioc.init(environment: Environment.dev);
  runApp(SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: DemoViewPage(),
    );
  }
}

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

1 回复

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


在Flutter中,依赖注入(Dependency Injection, DI)是一种设计模式,用于实现控制反转(Inversion of Control, IoC),它可以帮助你更好地管理代码的依赖关系,使代码更易于测试和维护。injectable 是一个流行的依赖注入库,而 injectable_mvvm 是一个结合了 injectable 和 MVVM 架构的插件。

1. 安装依赖

首先,你需要在 pubspec.yaml 文件中添加 injectableinjectable_mvvm 的依赖:

dependencies:
  flutter:
    sdk: flutter
  injectable: ^1.5.3
  injectable_mvvm: ^0.1.0

dev_dependencies:
  build_runner: ^2.1.0
  injectable_generator: ^1.5.3

2. 配置 injectable

在你的项目中创建一个 injection.dart 文件,并初始化 injectable

import 'package:injectable/injectable.dart';
import 'injection.config.dart';

@InjectableInit(
  initializerName: r'$initGetIt', // default
  preferRelativeImports: true, // default
  asExtension: false, // default
)
void configureDependencies() => $initGetIt(GetIt.instance);

然后运行 build_runner 生成代码:

flutter pub run build_runner build

这将生成 injection.config.dart 文件,其中包含了依赖注入的配置。

3. 使用 injectable_mvvm 实现 MVVM 架构

injectable_mvvm 提供了一些基类和工具来帮助你实现 MVVM 架构。以下是一个简单的示例:

3.1 创建 ViewModel

import 'package:injectable_mvvm/injectable_mvvm.dart';
import 'package:injectable/injectable.dart';

@injectable
class MyViewModel extends BaseViewModel {
  MyViewModel();

  void doSomething() {
    // 业务逻辑
  }
}

3.2 创建 View

import 'package:flutter/material.dart';
import 'package:injectable_mvvm/injectable_mvvm.dart';
import 'my_view_model.dart';

class MyView extends StatelessWidget {
  final MyViewModel viewModel;

  MyView({required this.viewModel});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('My View'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: viewModel.doSomething,
          child: Text('Do Something'),
        ),
      ),
    );
  }
}

3.3 在 main.dart 中配置依赖注入

import 'package:flutter/material.dart';
import 'package:injectable/injectable.dart';
import 'injection.dart';
import 'my_view.dart';
import 'my_view_model.dart';

void main() {
  configureDependencies();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyView(viewModel: getIt<MyViewModel>()),
    );
  }
}
回到顶部