Flutter 入门到头秃 - 依赖注入 Inject

本贴最后更新于 820 天前,其中的信息可能已经时移俗易

序言

依赖注入(IOC)就是通过容器,将当前这个类所需的对象实例化,而不需要这个类自身去实例化这个对象。目的是为了类的解耦。在小项目里面可能无法体现依赖注入的价值,但是在大型多人合作的项目里面,依赖注入能让整个项目更加健壮和易于维护。

Inject

说起依赖注入,最大名鼎鼎的莫过于 Java 的 Spring 系列。在 Flutter 开发中也有很多的依赖注入框架,其中官方推荐的框架就是本文的主角 Inject

导入

由于 Inject 不支持导包的形式,因此只能通过导入源码的方式引入。
在 lib 同级别目录新建 vendor 文件夹
在 vendor 文件夹里导入 inject 源码

git clone https://github.com/google/inject.dart.git

然后在 pubspec.yaml 文件中引用
注意 Inject 依赖于 build runner,因此也需要引入 build runner

dependencies:
  inject:
    path: ./vendor/inject.dart/package/inject

dev_dependencies:
  build_runner: ^1.0.0
  inject_generator:
    path: ./vendor/inject.dart/package/inject_generator

之后在项目根目录下创建一个名为 inject_generator.build.yaml 的文件复制以下配置内容

builders:
  inject_generator:
    target: ":inject_generator"
    import: "package:inject_generator/inject_generator.dart"
    builder_factories:
      - "summarizeBuilder"
      - "generateBuilder"
    build_extensions:
      ".dart":
        - ".inject.summary"
        - ".inject.dart"
    auto_apply: dependents
    build_to: source

为了隐藏生成的文件,请导航至 Android Studio-> Preferences-> Editor-> File Types 并将以下代码粘贴在 ignore files and folders 部分下
*.inject.summary;*.inject.dart;*.g.dart;

在 Visual Studio Code 中,导航到 Preferences-> Settings 并搜索 Files:Exclude。添加以下代码:

**/*.inject.summary
**/*.inject.dart
**/*.g.dart

简单入门

直接说原理和概念容易让人一头雾水,因此先展示一个简单的示例

首先写一个非常简单的 Presenter

@provide
class UserPresenter{

  String getUserName() {
    return "Nike";
  }

}

可以看到我们给这个类加了一个 @provide 的注解
provide 这个注解用于告诉 Inject,当前这个类是需要让 Inject 来帮助我们实例化的,当我们编译时 Inject 会帮我们把这个类放入注射器里面,当有类需要用到 UserPresenter 的实例化对象时,Inject 会再帮我们从注射器里将 UserPresenter 对象注入到该类。

接下来需要一个 Widget 来使用我们的 Presenter

@provide
class UserWidget extends StatelessWidget {

  UserPresenter _userPresenter;

  UserWidget(this._userPresenter);

  @override
  Widget build(BuildContext context) {
    return Text(_userPresenter.getUserName());
  }
  
}

使用这个 Presenter 也很简单,只需要在构造函数里传入即可,并且给该类加上 @provide 的注解。

这么写很好理解,但是下一个问题就来了,UserPresenter UserWidget 这两个对象既然不能通过直接 new 来实例化,那它们到底是在哪里被实例化的呢?

按照正常的 Ioc 来说,必然是有一个注射器来注入实例化对象的,Inject 当然也不例外,这里就要用到另外一个注解 @Injector() 来配置我们需要的注射器。

import 'app_injector.inject.dart' as g;

@Injector()
abstract class AppInjector {

  @provide
  UserWidget get userWidget;

  static Future<AppInjector> create() {
    return g.AppInjector$Injector.create();
  }
  
}

由于我们还没有编译,会出现找不到 app_injector.inject.dart 的错误提示

@Injector() 这个注解告诉 Inject,当前这个类是一个注射器,我们需要的实例都可以通过注射器来获取。比如当前我们需要 UserWidget 的实例。我们需要这么一行代码

 @provide
  UserWidget get userWidget;

现在基本的代码已经完成了,我们编译一下生成所需的代码
在命令行执行

flutter packages pub run build_runner build

最后 在 main 函数里使用该注射器

void main() async {
  final container = await AppInjector.create();
  runApp( MaterialApp(
    home: Scaffold(
      body: Center(child: container.userWidget),
    ),
  ));
}

可以看到,整个依赖注入已经完成了。

image.png

进阶

上面是比较常规的使用,Inject 还有别的注解

@singleton

这个注解的含义比较简单,让当前类成为一个单例,只会实例化一次。

@singleton
@provide
class UserWidget extends StatelessWidget {

我们给之前的 UserWidget 增加一个 singleton 注解,重新 build 之后
我们进入到自动生成的 app_injector.inject.dart 代码里面

  _i2.UserWidget _singletonUserWidget;
  
  _i2.UserWidget _createUserWidget() =>
      _singletonUserWidget ??= _i2.UserWidget(_createUserPresenter());

可以看到当 UserWidget 不为空时,会重用之前的对象。

@module

平时开发经常会有一个 module 里面包含多个 Repository 的写法,如果像上面一样把所有东西扔到 Injector 会使 Injector 变得很臃肿。我们可以通过 @module 注解来解决这个问题

首先 写两个有依赖的 Repository

@provide
class UserRepository {

  GoodRepository _goodRepository;

  UserRepository(this._goodRepository);

  Future<List<String>> getAllUsers() {
    print("getAllUsers");
    _goodRepository.getAllGoods();
      return null;
  }

}

@provide
class GoodRepository {

  Future<List<String>> getAllGoods() {
    print("getAllGoods");
    return null;
  }

}

然后 需要一个 module 来提供 Repository

@module
class UserService{

  @provide
  UserRepository userRepository(GoodRepository goodRepository) => UserRepository(goodRepository);

}

最后,在上面的 UserPresenter 里使用这个 Repository

@provide
class UserPresenter{

  UserRepository _userRepository;

  UserPresenter(this._userRepository);

  String getUserName() {
    _userRepository.getAllUsers();
    return "Nike";
  }

}

重新运行后,可以看到 UserRepository 也已经被成功注入了

image.png

  • Flutter

    Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 Flutter 可以与现有的代码一起工作,它正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。

    39 引用 • 92 回帖 • 5 关注
  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    499 引用 • 3597 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...