Flutter依赖注入插件injector_x的使用

Flutter依赖注入插件injector_x的使用

InjectorX

依赖管理来自Flutter。

InjectorX的想法是为了简化在使用Clean Architecture的Flutter项目中对依赖注入的控制和维护。与主要可用的包相比,InjectorX的主要区别在于通过上下文进行依赖注入控制,从而分散依赖注入,不在不需要的上下文中实例化不必要的对象。在这种模型中,对象本身就是一个服务定位器,用于其自身的注入,从而取代了通过控制器传递注入的需求,但并未失去解耦代码的能力,反而更容易查看被注入的对象。

思维导图

思维导图

图表表示的步骤:

首先,我们需要定义应用程序的契约。

在契约中规定了对象在实现时必须遵循的规则。为了使底层对象不依赖于具体的实现,而是依赖于契约,因此无论实现如何,只要遵循契约规则的对象都可以被接受到引用的注入中。

abstract class IApi {
  Future<dynamic> post(String url, dynamic data);
}

abstract class IUserRepo {
  Future<bool> saveUser(String email, String name);
}

abstract class IUserUsecase {
  Future<bool> call(String email, String name);
}

abstract class IViewModel {
  Future<bool> save(String email, String name);
  bool get inLoading;
}

/*
这个契约将在flutter_triple中进一步说明
*/
abstract class IViewModelTriple extends InjetorXViewModelStore<NotifierStore<Exception, int>> {
  Future<void> save(String email, String name);
}

/*
在这种情况下,我不需要继承自Inject,因为该对象的上下文不需要控制其注入,但是InjetorX可以在需要的地方注入它,例如接下来的UserRepoImpl示例。
*/
class ApiImpl implements IApi {
  @override
  Future post(String url, data) async {
    var httpClient = Dio();
    return await httpClient.post(url, data: data);
  }
}

/*
由于我们的仓库实现依赖于API契约,我们必须从Inject类继承,以便我们可以单独地操作此上下文的注入。
*/

class UserRepoImpl extends Inject<UserRepoImpl> implements IUserRepo {
  /*
  在此构造函数中,我们不需要传递需要注入的引用。这现在是通过针(Needles)来完成的。每个针(例如:Needle<IApi>())都会为合同做出必要的引用,以便InjectorX知道应该在该对象的上下文中注入什么。
  */
  UserRepoImpl() : super(needles: [Needle<IApi>()]);
  /*
  这里定义了将要被注入到该上下文中API合同的变量。
  */
  late IApi api;

  /*
  当类继承自Inject时,将自动创建此方法,它有一个InjectorX对象,它是识别和引用合同所需的注入的服务定位器。
  */
  @override
  void injector(InjectorX handler) {
    /*
    通过抽象方式,InjectorX的处理程序将搜索注册的API合同的实现。
    */
    api = handler.get();
  }

  /*
  我们将使用合同的实际实现,我们不知道是什么实现,也不需要知道,因为我们遵循合同规定的规则,这变得无关紧要。
  */
  @override
  Future<bool> saveUser(String email, String name) async {
    try {
      await api
          .post("https://api.com/user/save", {"email": email, "name": name});
      return true;
    } on Exception {
      return false;
    }
  }
}

/*
这里的一切都将重复前面的例子,但是在这里我们不知道UserRepoImpl在其上下文中注入了什么,只是引用了它的合同,而InjectorX将逐步知道在每个上下文中应该注入什么。
*/
class UserUsecaseImpl extends Inject<UserUsecaseImpl> implements IUserUsecase {
  UserUsecaseImpl() : super(needles: [Needle<IUserRepo>()]);

  late IUserRepo repo;
  /*
  用例的概念是为了控制特定行为的业务规则。在这个例子中,只允许保存带有gmail.com邮箱的用户。
  */
  @override
  Future<bool> call(String email, String name) async {
    if (email.contains("@gmail.com")) {
      return await repo.saveUser(email, name);
    } else {
      return false;
    }
  }

  @override
  void injector(InjectorX handler) {
    repo = handler.get();
  }
}

/*
ViewModel负责控制一个屏幕或特定widget的状态。请注意,ViewModel不控制业务规则,而是控制屏幕上任何被引用的状态。
在这种情况下,状态由RxNotifier控制,但这也可以使用您喜欢的任何其他状态管理器来实现。
*/
class ViewModelImpl extends Inject<ViewModelImpl> implements IViewModel {
  ViewModelImpl() : super(needles: [Needle<IUserUsecase>()]);

  late IUserUsecase userUsecase;
  var _inLoading = RxNotifier(false);

  set inLoading(bool v) => _inLoading.value = v;
  bool get inLoading => _inLoading.value;

  @override
  void injector(InjectorX handler) {
    userUsecase = handler.get();
  }

  @override
  Future<bool> save(String email, String name) async {
    var _result = false;

    inLoading = true;
    _result = await userUsecase(email, name);
    inLoading = false;

    return _result;
  }
}

/*
InjectorX还可以以简化的方式集成到flutter_triple中,从而更方便地通过流控制状态。
*/
class PresenterViewModel extends NotifierStore<Exception, int>
    with InjectCombinate<PresenterViewModel>
    implements IPresenterViewModel {
  PresenterViewModel() : super(0) {
    /*
    注意,现在构造函数中有一个小的不同,即init()。这是因为继承自InjectCombinate需要初始化,以便InjectorX知道哪些针负责注入合同。
    要了解有关flutter_triple的更多信息,请访问: https://pub.dev/packages/flutter_triple
   */
    init(needles: [Needle<IUsecase>()]);
  }
  /*
  现在,我们引用依赖项的方式不同,不再由injector(InjectorX handles)处理,而是采用这种新的方式通过inject()引用。
  */
  IUsecase get usecase => inject();

  @override
  bool increment() {
    update(usecase.increment(state));
    return true;
  }

  @override
  NotifierStore<Exception, int> getStore() {
    return this;
  }
}

/*
现在我们将进入视图的实现以展示完整的流程。
InjectorX有一个专门针对视图的功能。

在这个第一个示例中,将使用带有RxNotifier的ViewModel;

注意,现在不再需要实现以下方法:
@override
void injector(InjectorX handler) {
  userUsecase = handler.get();
}

对于视图来说,这是不同的。看看intiState()的新方法。
*/

class ScreenExample extends StatefulWidget
    with InjectCombinate<ScreenExample> {
  ScreenExample() {
     init(needles: [Needle<IViewModel>()])
  };
  @override
  _ScreenExampleState createState() => _ScreenExampleState();
}

class _ScreenExampleState extends State<ScreenExample> {
  late IViewModel viewModel;

  @override
  void initState() {
    super.initState();
    /*
    现在,我们不再使用injector方法中的handler,而是简单地调用widget.inject(),它将具有包含InjectorX资源的视图服务定位器。
    */
    viewModel = widget.inject();
  }

  @override
  Widget build(BuildContext context) {
    return RxBuilder(
      builder: (_) => IndexedStack(
        index: viewModel.inLoading ? 0 : 1,
        children: [
          Center(child: CircularProgressIndicator()),
          Center(
            child: ElevatedButton(
              onPressed: () async {
                var success =
                    await viewModel.save("mail@gmail.com", "Username");
                if (success) {
                  print("Users successful saved");
                } else {
                  print("Error on save user");
                }
              },
              child: Text("保存用户数据"),
            ),
          )
        ],
      ),
    );
  }
}

/*
这里还有一个使用flutter_triple的实现示例。本质上没有太多区别,除了我们如何处理状态变化。
*/
class ScreenTripleExample extends StatefulWidget
    with InjectCombinate<ScreenTripleExample> {
  ScreenTripleExample() {
     //别忘了启动injectorX
     init(needles: [Needle<IViewModel>()])
  };
  @override
  _ScreenTripleExampleState createState() => _ScreenTripleExampleState();
}

class _ScreenTripleExampleState extends State<ScreenTripleExample> {
  late IViewModelTriple viewModel;

  @override
  void initState() {
    super.initState();
    viewModel = widget.inject();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: ScopedBuilder(
        //注意,现在使用的是ViewModel实现的flutter_triple的getStore
        store: viewModel.getStore(),
        onState: (context, state) => Center(
          child: ElevatedButton(
            onPressed: () async {
              await viewModel.save("mail@gmail.com", "Username");
            },
            child: Text("保存用户数据"),
          ),
        ),
        onError: (context, error) => Center(child: Text(error.toString())),
        onLoading: (context) => Center(child: CircularProgressIndicator()),
      ),
    );
  }
}

合约参考

为了让InjectorX知道应该在每个针中注入什么,我们应该在应用初始化时向InjectorX显示每个合同的实现。请注意,在任何时候都没有传递实现给另一个引用的构造函数,即使该实现具有其自身的注入。 这将使依赖注入的控制变得非常简单,因为这样可以更容易地查看,并且不会一次性将所有对象加载到内存中,而是根据需求按需加载。

void _registerDependencies() {
  InjectorXBind.add<IApi>(() => ApiImpl());
  InjectorXBind.add<IUserRepo>(() => UserRepoImpl());
  InjectorXBind.add<IUserUsecase>(() => UserUsecaseImpl());
  InjectorXBind.add<IViewModel>(() => ViewModelImpl());
  InjectorXBind.add<IViewModelTriple>(() => ViewModelTriple());
}

如果这用GetIt会怎样?仅举一个简单的虚构示例

注意,这里通过构造函数传递注入的引用。在此示例中,由于是一个小示例,我们仍然可以轻松地查看,但是随着需要在一个构造函数中进行多次注入并且应用增长,这将成为一团糟,很难查看和控制正在注入的内容。 在这种情况下,即使不需要该引用,所有对象也会被加载到内存中。

void _setup() {
  GetIt.I.registerSingleton<IApi>(ApiImpl());
  GetIt.I.registerSingleton<IUserRepo>(UserRepoImpl( GetIt.I.get<IApi>() ));
  GetIt.I.registerSingleton<IUserUsecase>(UserUsecaseImpl( GetIt.I.get<IUserRepo>() ));
  GetIt.I.registerSingleton<IViewModel>(ViewModelImpl( GetIt.I.get<IUserUsecase>() ));
  GetIt.I.registerSingleton<IViewModelTriple>(ViewModelTriple( GetIt.I.get<IUserUsecase>() ));
}

InjectorX不依赖于使用GetIt的特定调用,每次我们需要从其包中检索已注册的对象时,都是通过以下示例完成的:

var viewModel = GetIt.I.get<IViewModel>();

如果不这样做,所有需要自动注入的引用都不会工作。

在InjectorX中,我可以自由地以两种方式进行: 使用依赖管理器如下:

IViewModel viewModel = InjectorXBind.get();

或者直接实例化类:

/* 
ViewModel依赖于IUserUsecase,它由UserUsecaseImpl实现,后者又依赖于IUserRepo,它由UserRepoImpl实现,后者又依赖于IApi,它由ApiImpl实现。依赖关系控制是在每个上下文中分阶段进行的,因此直接实例化类没有任何影响。尽管如此,所有需要在此上下文中注入的内容都将以无问题的方式注入。
*/

var viewModel = ViewModelImpl();

注册为单例

void _registerDependencies() {
  InjectorXBind.add<IApi>(() => ApiImpl(), singleton: true);
  InjectorXBind.add<IUserRepo>(() => UserRepoImpl(), singleton: true);
  InjectorXBind.add<IUserUsecase>(() => UserUsecaseImpl(), singleton: true);
  InjectorXBind.add<IViewModel>(() => ViewModelImpl(), singleton: true);
  InjectorXBind.add<IViewModelTriple>(() => ViewModelTriple(), singleton: true);
}

通过这种方式,合同被引用为单例,但是只有当某个底层对象需要使用它时,单例才会生成一个实例。否则,对象不会放入内存中。

即使注册为单例也实例化新对象

有两种方法可以做到这一点,一种是通过InjetorXBind如下:

IViewModel viewModel = InjectorXBind.get(newInstance: true);

通过上述示例,即使在InjetorXBind中已作为单例注册,此调用也将返回对象的新实例;

然而,这可能是因为某个对象的针需要其所有注入每次都重新实例化。

例如在IUserRepo的情况下:

class UserRepoImpl extends Inject<UserRepoImpl> implements IUserRepo {
  /*
  注意Needle<IApi>中的newInstance: true参数
  这意味着即使InjectorXBind已将此合同注册为单例,在此对象中这将被忽略并始终返回ApiImpl的新实例。
  */
  UserRepoImpl() : super(needles: [Needle<IApi>(newInstance: true)]);
  late IApi api;
  @override
  void injector(InjectorX handler) {
    api = handler.get();
  }
  @override
  Future<bool> saveUser(String email, String name) async {
    try {
      await api
          .post("https://api.com/user/save", {"email": email, "name": name});
      return true;
    } on Exception {
      return false;
    }
  }
}

测试和模拟注入

向UserRepoImp注入ApiMock 有两种方法可以做到这一点,一种是通过InjectorXBind.get,另一种是直接实例化类。 在此示例中,我使用了Mockito来构建模拟。

class ApiMock extends Mock implements IApi {}
void main() {

  _registerDependencies();
  
  late ApiMock apiMock;
  
  setUp(() {
     apiMock = ApiMock();
  });

   /*
   使用InjectorXBind.get的示例;
   */
  test("test use InjectorXBind.get", () async {

    when(apiMock.post("", "")).thenAwswer((_) async => true);
    /*
    使用测试对象的injectMocks来替换其上下文内的注入,仅测试并注入属于测试对象本身的唯一内容,完全忽略不属于该特定上下文的所有内容。
    */
    var userRepoImp = (InjectorXBind.get<IUserRepo>() as UserRepoImpl).injectMocks([NeedleMock<IApi>(mock: apiMock)]);
    var res = await userRepoImp.saveUser("", "");
    expect(res, isTrue);
  });

   /*
   直接实例化的示例;
   */
  test("test use direct implement instance", () async {

    when(apiMock.post("", "")).thenAwswer((_) async => true);
    /*
    使用测试对象的injectMocks来替换其上下文内的注入,仅测试并注入属于测试对象本身的唯一内容,完全忽略不属于该特定上下文的所有内容。
    因此,编写起来更加简化,但最终结果相同
    */
    var userRepoImp = UserRepoImpl().injectMocks([NeedleMock<IApi>(mock: apiMock)]);
    var res = await userRepoImp.saveUser("", "");
    expect(res, isTrue);
  });
}

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

1 回复

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


injector_x 是一个轻量级的依赖注入(Dependency Injection, DI)插件,适用于 Flutter 应用。它可以帮助你更轻松地管理和注入依赖项,从而使代码更具可测试性和可维护性。以下是如何在 Flutter 项目中使用 injector_x 的基本步骤。

1. 添加依赖

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

dependencies:
  flutter:
    sdk: flutter
  injector_x: ^1.0.0  # 请使用最新版本

然后运行 flutter pub get 来安装依赖。

2. 创建依赖注入容器

injector_x 提供了一个 Injector 类来管理依赖项。你可以创建一个全局的 Injector 实例,或者根据需要创建多个实例。

import 'package:injector_x/injector_x.dart';

final injector = Injector();

3. 注册依赖项

你可以使用 register 方法来注册依赖项。injector_x 支持以下几种注册方式:

  • 单例模式(Singleton):在整个应用生命周期中只创建一个实例。
  • 工厂模式(Factory):每次请求时都创建一个新的实例。
  • 延迟单例(Lazy Singleton):在第一次请求时创建实例,之后每次请求都返回同一个实例。
// 注册一个单例
injector.register<MyService>(() => MyServiceImpl(), isSingleton: true);

// 注册一个工厂
injector.register<MyRepository>(() => MyRepositoryImpl());

// 注册一个延迟单例
injector.register<MyManager>(() => MyManagerImpl(), isLazySingleton: true);

4. 获取依赖项

你可以使用 get 方法来获取已注册的依赖项。

final myService = injector.get<MyService>();
final myRepository = injector.get<MyRepository>();
final myManager = injector.get<MyManager>();

5. 使用依赖注入

在 Flutter 的 Widget 中,你可以使用 Injector 来获取依赖项并注入到你的组件中。

class MyHomePage extends StatelessWidget {
  final MyService myService = injector.get<MyService>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Injector_X Example'),
      ),
      body: Center(
        child: Text(myService.doSomething()),
      ),
    );
  }
}

6. 清理依赖项

在某些情况下,你可能需要清理依赖项。你可以使用 unregister 方法来取消注册依赖项。

injector.unregister<MyService>();

7. 使用 InjectorX 进行模块化

你还可以将依赖项注册逻辑封装到不同的模块中,然后在主模块中导入这些模块。

class MyAppModule {
  static void init() {
    injector.register<MyService>(() => MyServiceImpl(), isSingleton: true);
    injector.register<MyRepository>(() => MyRepositoryImpl());
  }
}

void main() {
  MyAppModule.init();
  runApp(MyApp());
}

8. 测试中使用 InjectorX

在测试中,你可以轻松地替换依赖项,以便更好地控制测试环境。

void main() {
  setUp(() {
    injector.register<MyService>(() => MockMyService(), isSingleton: true);
  });

  tearDown(() {
    injector.unregister<MyService>();
  });

  test('MyService test', () {
    final myService = injector.get<MyService>();
    expect(myService.doSomething(), 'Mocked Response');
  });
}
回到顶部