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
更多关于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');
});
}