mcpField
F
flutter-di-get-it

flutter-di-get-it

github.com/junsuk5开发2444 次安装24 周前更新
代码仓库

关于 flutter-di-get-it

面向 Flutter 项目的 get_it 依赖注入指南,规定在单一入口按依赖顺序注册对象,并从 Root 小部件取用 ViewModel。通过为 ViewModel 使用工厂注册,避免不同页面共享同一实例。

主要功能

  1. 01集中管理依赖注册入口
  2. 02按依赖图顺序注册对象
  3. 03区分单例与工厂注册
  4. 04仅在 Root 小部件获取 ViewModel
  5. 05通过接口类型注入依赖

适用场景

  1. 01为 Flutter 应用设置依赖注入
  2. 02注册数据源、Repository 和 UseCase
  3. 03确保每次打开界面创建新 ViewModel
  4. 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