关于 flutter-di-get-it
面向 Flutter 项目的 get_it 依赖注入指南,规定在单一入口按依赖顺序注册对象,并从 Root 小部件取用 ViewModel。通过为 ViewModel 使用工厂注册,避免不同页面共享同一实例。
主要功能
- 01集中管理依赖注册入口
- 02按依赖图顺序注册对象
- 03区分单例与工厂注册
- 04仅在 Root 小部件获取 ViewModel
- 05通过接口类型注入依赖
适用场景
- 01为 Flutter 应用设置依赖注入
- 02注册数据源、Repository 和 UseCase
- 03确保每次打开界面创建新 ViewModel
- 04保持 Screen 小部件便于测试和预览
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ junsuk5/ survival-flutter-skills --skill flutter-di-get-it安装后在 Claude Code 中输入/flutter-di-get-it确认已加载。
详细介绍
Flutter DI — get_it
原则
- DI 在单一入口
lib/core/di/di_setup.dart的diSetup()函数中注册。不要按 Feature 分散注册(因为这是单一包结构)。 - 必须在
main()中调用runApp()之前调用diSetup()。 - 只在 Root Widget 中使用
getIt<T>()获取 ViewModel。绝对不要在 Screen Widget 或通用 Widget 中使用getIt(确保易于测试)。 - ViewModel 始终使用
registerFactory注册。若注册为单例,多个屏幕就会共享同一状态。
基本入口
// lib/core/di/di_setup.dart
final getIt = GetIt.instance;
void diSetup() {
// 1) data source
// 2) repository
// 3) use case
// 4) view model
}
// lib/main.dart
void main() {
diSetup();
runApp(const MyApp());
}
注册顺序应从依赖图的叶节点逐步向上。顺序必须是 DataSource → Repository → UseCase → ViewModel,这样 getIt() 引用时,对象就已经注册完成。
各层注册模式
DataSource(单例)
getIt.registerSingleton<RecipeDataSource>(RemoteRecipeDataSourceImpl());
getIt.registerSingleton<LocalStorage>(DefaultLocalStorage());
使用接口类型注册,确保 Root/ViewModel 不会直接引用实现类。
Repository(单例)
使用 getIt() 解析构造函数参数并传入。需要泛型推断时,显式写作 getIt<Foo>()。
getIt.registerSingleton<RecipeRepository>(
MockRecipeRepositoryImpl(
recipeDataSource: getIt(),
),
);
getIt.registerSingleton<BookmarkRepository>(MockBookmarkRepositoryImpl());
UseCase(单例)
它是无状态的纯功能,使用单例即可。类型参数可以省略(因为只有一个实现类,Dart 会进行推断)。
getIt.registerSingleton(
GetSavedRecipesUseCase(
recipeRepository: getIt(),
bookmarkRepository: getIt(),
),
);
ViewModel(工厂)
必须使用 registerFactory。 因为重新打开屏幕时需要创建新实例。
getIt.registerFactory<IngredientViewModel>(
() => IngredientViewModel(
ingredientRepository: getIt(),
procedureRepository: getIt(),
getDishesByCategoryUseCase: getIt(),
clipboardService: getIt(),
),
);
在 Root Widget 中注入
class SavedRecipesRoot extends StatelessWidget {
const SavedRecipesRoot({super.key});
@override
Widget build(BuildContext context) {
final viewModel = getIt<SavedRecipesViewModel>();
return ListenableBuilder(
listenable: viewModel,
builder: (context, _) => SavedRecipesScreen(
recipes: viewModel.state.recipes,
onAction: viewModel.onAction,
),
);
}
}
原则:只在 *Root Widget 的 build() 中调用 getIt<>()。保持 Screen Widget 只接收 state 和 onAction,以便进行预览和测试。
单例与工厂
| 注册函数 | 何时使用 | 示例 |
|---|---|---|
registerSingleton<T>(instance) | 应用生命周期内只需要一个实例。用于直接传入已创建的对象 | Repository、DataSource、UseCase、HttpClient |
registerLazySingleton<T>(() => ...) | 希望延迟到首次访问时才创建 | 初始化成本较高的对象 |
registerFactory<T>(() => ...) | 每次调用都创建新实例 | ViewModel |
在本项目的实际示例(lib/core/di/di_setup.dart)中,Repository/DataSource/UseCase 始终使用 registerSingleton 注册,ViewModel 则使用 registerFactory。请遵循此规则。
需要工厂方法的情况
如果不能只靠构造函数完成创建,而需要调用工厂方法,请使用 Lambda 形式。
getIt.registerSingleton<HttpClient>(HttpClientFactory.create(getIt()));
需要命名参数时,也同样使用 Lambda 形式。
检查清单 — 添加新 feature 时注册 DI
- DataSource 接口定义在
domain或data/data_source/中 - 使用
registerSingleton<Interface>注册实现类(显式指定接口类型) - 注册 Repository 接口/实现类
- 注册 UseCase(无状态时使用单例)
- ViewModel 始终使用
registerFactory - 只在 Root Widget 中使用
getIt<ViewModelType>()获取实例
反模式
- ❌ 在 Screen Widget、通用 Widget 或 StatelessWidget 树的深层调用
getIt<T>()→ 将无法进行测试和预览。 - ❌ 使用
registerSingleton注册 ViewModel → 重新进入屏幕时会导致状态污染。 - ❌ 在
diSetup()之外偷偷调用getIt.register*→ 注册顺序和依赖图会变得分散。 - ❌ 在
getIt中使用实现类类型获取对象(getIt<MockRecipeRepositoryImpl>())→ 应使用接口类型获取,以便替换实现类。
评论
支持 Markdown