Flutter 三方库 get_it injectable 的鸿蒙化适配指南实现优雅的依赖注入欢迎加入开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.net大家好呀 今天要和大家分享一个超级实用的Flutter开发技巧——如何将 get_it 与 injectable 这对黄金搭档完美适配到鸿蒙平台上让我们的代码既优雅又高效话说在实际项目开发中依赖管理一直是个让人又爱又恨的话题呢。当项目规模越来越大时各个模块之间的依赖关系就像一团乱麻一不小心就会陷入循环依赖的噩梦。而今天要介绍的 get_it 和 injectable就像是那把解开乱麻的神奇钥匙它们能够帮我们实现自动化的依赖注入让代码结构瞬间变得清晰明了✨一、为什么选择 get_it injectable在开始动手之前小伙伴们肯定会有疑问Flutter 自带的 Provider 不是挺好的吗为什么还要引入 get_it 和 injectable 呢好问题让姐姐来给大家分析分析~首先呢get_it 是一个轻量级的服务定位器Service Locator它采用了单例模式和依赖注册的方式来管理对象访问。相比 Provider 的发布-订阅模式get_it 的核心优势在于依赖查找发生在运行时但却是通过静态类型安全的方式进行的。这意味着我们可以在任何地方通过类型引用获取到已注册的服务实例就像变魔术一样方便而 injectable 则是 get_it 的最佳拍档它是一个代码生成器能够自动分析我们的服务类构造函数生成正确的依赖注册代码。想象一下当我们需要注册一个需要 5 个依赖的服务时传统方式需要手动写一长串注册代码而使用 injectable 只需要一个injectable注解就能搞定是不是超级方便更重要的是这套组合在鸿蒙平台上表现非常稳定经过实测验证get_it 的反射能力在 OHOpenHarmony上能够正常工作服务单例的生命周期管理也完全符合预期。现在就让我们一起来看看如何在 Flutter for OpenHarmony 项目中使用这对黄金组合吧~二、项目架构设计2.1 整体架构规划在开始编码之前我们先来设计一下整体的项目架构。一个好的架构能够让代码更加清晰、易于维护这也是我们引入依赖注入框架的核心目标之一。本项目采用分层架构的设计思想将应用划分为三个主要层次表现层Presentation Layer负责 UI 渲染和用户交互这是 Flutter 的老本行啦~业务逻辑层Business Logic Layer处理核心业务逻辑我们的服务类就放在这一层数据层Data Layer负责数据获取和持久化比如 API 服务、本地存储等通过 get_it injectable我们将实现一套松耦合的依赖管理方案让各层之间通过接口而非实现进行通信。这样做的好处是当需要替换某个服务的实现时比如从本地存储切换到网络存储无需修改调用方的代码只需要修改依赖注册的地方即可2.2 项目结构规划lib/ ├── main.dart # 应用入口 ├── injection.dart # 依赖注入配置 ├── services/ # 服务层 │ ├── api_service.dart # API 服务 │ └── storage_service.dart # 存储服务 ├── models/ # 数据模型 │ └── todo_item.dart # 待办事项模型 └── providers/ # Riverpod Providers └── todo_provider.dart # 待办事项状态管理三、核心实现步骤3.1 添加项目依赖首先呢我们需要在 pubspec.yaml 中添加 get_it、injectable 和它们的代码生成工具dependencies:flutter:sdk:fluttercupertino_icons:^1.0.8get_it:^8.0.3injectable:^2.5.0dev_dependencies:flutter_test:sdk:flutterflutter_lints:^5.0.0build_runner:^2.4.15injectable_generator:^2.7.0添加完依赖后执行flutter pub get将包下载到本地。接下来就是见证奇迹的时刻啦~3.2 定义数据模型让我们先定义一个简单的待办事项数据模型这个模型将贯穿整个教程classTodoItem{finalint userId;finalint id;finalStringtitle;finalbool completed;constTodoItem({requiredthis.userId,requiredthis.id,requiredthis.title,requiredthis.completed,});factoryTodoItem.fromJson(MapString,dynamicjson){returnTodoItem(userId:json[userId]asint,id:json[id]asint,title:json[title]asString,completed:json[completed]asbool,);}MapString,dynamictoJson(){return{userId:userId,id:id,title:title,completed:completed,};}TodoItemcopyWith({int?userId,int?id,String?title,bool?completed,}){returnTodoItem(userId:userId??this.userId,id:id??this.id,title:title??this.title,completed:completed??this.completed,);}}这个模型使用了不可变设计final 字段 copyWith 方法这是 Flutter 社区推荐的最佳实践哦~不可变对象不仅线程安全还能帮助我们避免很多潜在的 bug3.3 创建服务接口与实现现在是 get_it injectable 大显身手的时候了我们先定义服务接口然后创建实现类。这种面向接口编程的设计是依赖注入的核心思想呢~首先定义一个抽象的 TodoRepository 接口abstractclassTodoRepository{FutureListTodoItemgetTodos();FutureTodoItemgetTodoById(int id);FutureTodoItemcreateTodo(TodoItemtodo);FutureTodoItemupdateTodo(TodoItemtodo);FuturevoiddeleteTodo(int id);}接下来创建这个接口的具体实现类并使用 injectable注解标记injectableclassTodoRepositoryImplimplementsTodoRepository{finalStringbaseUrl;TodoRepositoryImpl(Named(apiBaseUrl)this.baseUrl);overrideFutureListTodoItemgetTodos()async{awaitFuture.delayed(constDuration(milliseconds:800));returnList.generate(10,(index)TodoItem(userId:1,id:index1,title:待办事项${index1},completed:index%30,),);}overrideFutureTodoItemgetTodoById(int id)async{awaitFuture.delayed(constDuration(milliseconds:500));returnTodoItem(userId:1,id:id,title:待办事项$id,completed:false,);}overrideFutureTodoItemcreateTodo(TodoItemtodo)async{awaitFuture.delayed(constDuration(milliseconds:600));returntodo;}overrideFutureTodoItemupdateTodo(TodoItemtodo)async{awaitFuture.delayed(constDuration(milliseconds:600));returntodo;}overrideFuturevoiddeleteTodo(int id)async{awaitFuture.delayed(constDuration(milliseconds:500));}}注意到了吗这里使用 Named注解来区分不同的 String 参数这是因为有时候我们的服务需要多个同类型的配置参数 Named能够帮助我们精确地指定要注入哪一个~3.4 配置依赖注入模块现在是最关键的部分——配置 injectable 的依赖注册模块创建一个名为injection.dart的文件importpackage:get_it/get_it.dart;importpackage:injectable/injectable.dart;importinjection.config.dart;finalgetItGetIt.instance;moduleabstractclassRegisterModule{lazySingletonNamed(apiBaseUrl)StringgetapiBaseUrlhttps://jsonplaceholder.typicode.com;lazySingletonNamed(appName)StringgetappNameTodo App;}InjectableInit(initializerName:init,preferRelativeImports:true,asExtension:true,)FuturevoidconfigureDependencies()asyncgetIt.init();这里有几个小技巧要分享给小伙伴们module 注解告诉 injectable 这是一个模块类用于注册依赖lazySingleton延迟单例模式只在首次使用时创建实例之后一直复用InjectableInit自动初始化所有带有injectable注解的类3.5 运行代码生成器激动人心的时刻到了现在我们需要在项目根目录下运行代码生成器flutter pub run build_runner build --delete-conflicting-outputs运行成功后会在injection.dart旁边生成一个injection.config.dart文件。这个文件包含了所有服务类的依赖注册代码是不是很神奇生成的代码大概长这样以实际生成为准// GENERATED CODE - DO NOT MODIFY BY HAND// **************************************************************************// InjectableConfigGenerator// **************************************************************************// ignore_for_file: typelint// coverage:ignore-file// ignore_for_file: no_leading_underscores_for_library_prefixed_identifiersimportpackage:get_it/get_it.dartas_i174;importpackage:injectable/injectable.dartas_i526;importinjection.dartas_i894;extensionGetItInjectableXon_i174.GetIt{_i174.GetItinit({String?environment,_i526.EnvironmentFilter?environmentFilter,}){finalgh_i526.GetItHelper(this,environment,environmentFilter,);gh.lazySingleton_i894.TodoRepositoryImpl(()_i894.TodoRepositoryImpl(gh_i894.String(param1:apiBaseUrl)));returnthis;}}3.6 在应用中使用依赖注入一切准备就绪现在让我们看看如何在应用中使用这套依赖注入系统~importpackage:flutter/material.dart;importinjection.dart;importservices/todo_repository.dart;pragma(vm:entry-point)voidmain()async{WidgetsFlutterBinding.ensureInitialized();awaitconfigureDependencies();runApp(constTodoApp());}classTodoAppextendsStatelessWidget{constTodoApp({super.key});overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:get_it injectable Demo,theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.pinkAccent),useMaterial3:true,),home:constTodoListPage(),);}}classTodoListPageextendsStatefulWidget{constTodoListPage({super.key});overrideStateTodoListPagecreateState()_TodoListPageState();}class_TodoListPageStateextendsStateTodoListPage{latefinalTodoRepository_todoRepository;ListTodoItem_todos[];bool _isLoadingfalse;String?_errorMessage;overridevoidinitState(){super.initState();_todoRepositorygetItTodoRepository();_loadTodos();}Futurevoid_loadTodos()async{setState((){_isLoadingtrue;_errorMessagenull;});try{finaltodosawait_todoRepository.getTodos();setState((){_todostodos;_isLoadingfalse;});}catch(e){setState((){_errorMessagee.toString();_isLoadingfalse;});}}Futurevoid_toggleComplete(TodoItemtodo)async{finalupdatedtodo.copyWith(completed:!todo.completed);await_todoRepository.updateTodo(updated);_loadTodos();}overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:Text(getItString(param1:appName)),backgroundColor:Colors.pinkAccent.shade100,),body:_buildBody(),floatingActionButton:FloatingActionButton(onPressed:_loadTodos,child:constIcon(Icons.refresh),),);}Widget_buildBody(){if(_isLoading){returnconstCenter(child:CircularProgressIndicator(),);}if(_errorMessage!null){returnCenter(child:Column(mainAxisAlignment:MainAxisAlignment.center,children:[constIcon(Icons.error_outline,size:64,color:Colors.red),constSizedBox(height:16),Text(_errorMessage!),constSizedBox(height:16),ElevatedButton(onPressed:_loadTodos,child:constText(重试),),],),);}returnListView.builder(itemCount:_todos.length,itemBuilder:(context,index){finaltodo_todos[index];returnCard(margin:constEdgeInsets.symmetric(horizontal:16,vertical:8),child:ListTile(leading:Checkbox(value:todo.completed,onChanged:(_)_toggleComplete(todo),),title:Text(todo.title,style:TextStyle(decoration:todo.completed?TextDecoration.lineThrough:null,),),trailing:IconButton(icon:constIcon(Icons.delete_outline),onPressed:()async{await_todoRepository.deleteTodo(todo.id);_loadTodos();},),),);},);}}看我们只需要在需要的地方调用getItTodoRepository()就能获取到注册的服务实例啦完全不需要手动new对象也不用担心循环依赖的问题四、鸿蒙平台适配验证4.1 单例生命周期测试在鸿蒙设备上运行应用时单例模式的行为需要特别关注。我们通过日志验证了以下几点应用启动时get_it 注册的 lazySingleton 并不会立即创建实例首次访问时实例被创建并缓存后续访问返回同一个实例引用应用关闭时实例被正确释放这个行为在鸿蒙平台上与 Android 平台完全一致说明 get_it 的单例管理在 OH 上工作正常4.2 反射能力验证injectable 依赖 Dart 的反射机制来生成代码。经验证代码生成器在鸿蒙项目上能够正常运行生成的配置代码与标准 Flutter 项目无异。运行时依赖查找也完全符合预期没有出现任何反射相关的异常。五、实战经验总结经过实际的鸿蒙化适配项目验证get_it injectable 这套组合在 OpenHarmony 平台上的表现非常稳定。以下是姐姐总结的几个实战小技巧分享给大家~技巧一善用 lazySingleton而不是 singleton。lazySingleton 只在首次使用时创建实例能够加快应用启动速度特别适合那些初始化比较耗时的服务。技巧二对于需要初始化的服务可以使用registerSingletonAsync。这样可以确保服务在应用完全初始化后再被使用moduleabstractclassAsyncRegisterModule{preResolvesingletonFutureDatabaseServicegetdatabaseasync{returnawaitDatabaseService.initialize();}}技巧三在测试环境中可以使用GetIt.reset()重置所有注册然后重新注册 mock 实现这样能够实现完美的单元测试技巧四如果项目中有多个环境开发、测试、生产可以使用 environment 参数来注册不同环境的依赖moduleabstractclassRegisterModule{devlazySingletonApiServicegetdevApiServiceDevApiService();prodlazySingletonApiServicegetprodApiServiceProdApiService();}六、与 Provider 的集成方案有小伙伴可能会问我们的项目已经在使用 Provider 了能不能把 get_it injectable 和 Provider 结合起来使用呢当然可以啦其实这两种方案并不冲突反而可以相辅相成。get_it injectable 负责管理服务层的依赖而 Provider 则负责 UI 状态管理。一个比较好的实践是使用 get_it injectable 注入 Repository 和 Service在 Provider 中使用这些注入的服务UI 层通过 Provider 获取状态这样既能发挥 Provider 在状态管理方面的优势又能享受依赖注入带来的解耦便利简直是双剑合璧✨这是我的运行截图七、结语好啦~今天的分享就到这里啦 通过本文小伙伴们应该已经掌握了在 Flutter for OpenHarmony 项目中使用 get_it injectable 实现依赖注入的方法。这套组合不仅代码优雅而且经过实测验证在鸿蒙平台上运行完全稳定可靠最后还是要提醒大家依赖注入虽然好用但也不要过度使用哦~对于一些简单的场景直接使用final声明反而更加直观。架构设计的核心目标是让代码更易维护而不是炫技适合的才是最好的如果小伙伴们在实践过程中遇到任何问题欢迎到社区来讨论交流~我们下期再见啦