flutter-di-get-it

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Flutter DI — get_it

Flutter DI — get_it

원칙

原则

  • DI는 단일 엔트리
    lib/core/di/di_setup.dart
    diSetup()
    함수 하나에서 등록한다. Feature별로 분산하지 않는다 (단일 패키지 구조이므로).
  • main()
    에서
    runApp()
    호출 전에
    diSetup()
    을 반드시 호출한다.
  • Root 위젯에서만
    getIt<T>()
    로 ViewModel을 꺼낸다. Screen 위젯이나 공용 위젯에는 절대
    getIt
    을 쓰지 않는다 (테스트 용이성 확보).
  • ViewModel은 항상
    registerFactory
    로 등록한다. 싱글톤이면 여러 화면이 같은 상태를 공유하게 되어 버린다.

  • DI需在单一入口文件
    lib/core/di/di_setup.dart
    diSetup()
    函数中统一注册,不按功能模块(Feature)分散注册(因采用单一包结构)。
  • 必须在
    main()
    中调用
    runApp()
    之前执行
    diSetup()
  • 仅在Root组件中通过
    getIt<T>()
    获取ViewModel。绝对不能在Screen组件或公共组件中使用
    getIt
    (确保测试便利性)。
  • ViewModel**必须始终通过
    registerFactory
    **注册。若使用单例,多个页面会共享同一状态。

기본 진입점

基本入口

dart
// lib/core/di/di_setup.dart
final getIt = GetIt.instance;

void diSetup() {
  // 1) data source
  // 2) repository
  // 3) use case
  // 4) view model
}
dart
// lib/main.dart
void main() {
  diSetup();
  runApp(const MyApp());
}
등록 순서는 의존성 그래프의 리프부터 위로 올린다. DataSource → Repository → UseCase → ViewModel 순서여야
getIt()
이 참조할 때 이미 등록되어 있다.

dart
// lib/core/di/di_setup.dart
final getIt = GetIt.instance;

void diSetup() {
  // 1) data source
  // 2) repository
  // 3) use case
  // 4) view model
}
dart
// lib/main.dart
void main() {
  diSetup();
  runApp(const MyApp());
}
注册顺序需从依赖图的叶子节点开始向上进行。必须遵循DataSource → Repository → UseCase → ViewModel的顺序,这样
getIt()
引用时才能确保对象已完成注册。

레이어별 등록 패턴

分层注册模式

DataSource (싱글톤)

DataSource(单例)

dart
getIt.registerSingleton<RecipeDataSource>(RemoteRecipeDataSourceImpl());
getIt.registerSingleton<LocalStorage>(DefaultLocalStorage());
인터페이스 타입으로 등록해 구현체를 Root/ViewModel에서 절대 직접 참조하지 않게 만든다.
dart
getIt.registerSingleton<RecipeDataSource>(RemoteRecipeDataSourceImpl());
getIt.registerSingleton<LocalStorage>(DefaultLocalStorage());
以接口类型注册,确保Root/ViewModel不会直接引用实现类。

Repository (싱글톤)

Repository(单例)

생성자 파라미터는
getIt()
으로 풀어서 전달한다. 제네릭 추론이 필요한 경우 명시적으로
getIt<Foo>()
로 쓴다.
dart
getIt.registerSingleton<RecipeRepository>(
  MockRecipeRepositoryImpl(
    recipeDataSource: getIt(),
  ),
);
getIt.registerSingleton<BookmarkRepository>(MockBookmarkRepositoryImpl());
构造函数参数通过
getIt()
解析传递。需要泛型推断时,显式使用
getIt<Foo>()
dart
getIt.registerSingleton<RecipeRepository>(
  MockRecipeRepositoryImpl(
    recipeDataSource: getIt(),
  ),
);
getIt.registerSingleton<BookmarkRepository>(MockBookmarkRepositoryImpl());

UseCase (싱글톤)

UseCase(单例)

상태를 갖지 않는 순수 기능이므로 싱글톤으로 충분하다. 타입 파라미터는 생략해도 된다 (구현 클래스가 하나뿐이므로 Dart 추론이 된다).
dart
getIt.registerSingleton(
  GetSavedRecipesUseCase(
    recipeRepository: getIt(),
    bookmarkRepository: getIt(),
  ),
);
UseCase是无状态的纯功能模块,使用单例即可。类型参数可省略(因仅存在一个实现类,Dart可自动推断)。
dart
getIt.registerSingleton(
  GetSavedRecipesUseCase(
    recipeRepository: getIt(),
    bookmarkRepository: getIt(),
  ),
);

ViewModel (팩토리)

ViewModel(工厂)

반드시
registerFactory
를 쓴다.
화면을 다시 열 때 새 인스턴스가 필요하기 때문이다.
dart
getIt.registerFactory<IngredientViewModel>(
  () => IngredientViewModel(
    ingredientRepository: getIt(),
    procedureRepository: getIt(),
    getDishesByCategoryUseCase: getIt(),
    clipboardService: getIt(),
  ),
);

必须使用
registerFactory
。因为重新打开页面时需要新的实例。
dart
getIt.registerFactory<IngredientViewModel>(
  () => IngredientViewModel(
    ingredientRepository: getIt(),
    procedureRepository: getIt(),
    getDishesByCategoryUseCase: getIt(),
    clipboardService: getIt(),
  ),
);

Root 위젯에서 주입하기

在Root组件中注入

dart
class SavedRecipesRoot extends StatelessWidget {
  const SavedRecipesRoot({super.key});

  
  Widget build(BuildContext context) {
    final viewModel = getIt<SavedRecipesViewModel>();
    return ListenableBuilder(
      listenable: viewModel,
      builder: (context, _) => SavedRecipesScreen(
        recipes: viewModel.state.recipes,
        onAction: viewModel.onAction,
      ),
    );
  }
}
원칙:
getIt<>()
호출은
*Root
위젯의
build()
안에서만 일어난다. Screen 위젯은
state
onAction
만 받도록 유지해 프리뷰/테스트가 가능하게 한다.

dart
class SavedRecipesRoot extends StatelessWidget {
  const SavedRecipesRoot({super.key});

  
  Widget build(BuildContext context) {
    final viewModel = getIt<SavedRecipesViewModel>();
    return ListenableBuilder(
      listenable: viewModel,
      builder: (context, _) => SavedRecipesScreen(
        recipes: viewModel.state.recipes,
        onAction: viewModel.onAction,
      ),
    );
  }
}
原则
getIt<>()
仅在
*Root
组件的
build()
方法内调用。Screen组件仅接收
state
onAction
,确保其可预览、可测试。

싱글톤 vs 팩토리

单例 vs 工厂

등록 함수언제 쓰나
registerSingleton<T>(instance)
앱 수명 동안 한 인스턴스만 필요. 이미 생성한 객체를 바로 넣을 때Repository, DataSource, UseCase, HttpClient
registerLazySingleton<T>(() => ...)
최초 접근 시점까지 생성을 늦추고 싶을 때초기화 비용이 큰 객체
registerFactory<T>(() => ...)
호출할 때마다 새 인스턴스ViewModel
이 프로젝트의 실제 예(
lib/core/di/di_setup.dart
)에서는 Repository/DataSource/UseCase는
registerSingleton
, ViewModel은
registerFactory
로 일관되게 분리되어 있다. 이 규칙을 따르라.

注册函数使用场景示例
registerSingleton<T>(instance)
应用生命周期内仅需一个实例,直接传入已创建的对象时Repository、DataSource、UseCase、HttpClient
registerLazySingleton<T>(() => ...)
希望延迟到首次访问时再创建实例时初始化成本较高的对象
registerFactory<T>(() => ...)
每次调用都需要新实例时ViewModel
本项目的实际示例(
lib/core/di/di_setup.dart
)中,Repository/DataSource/UseCase统一使用
registerSingleton
,ViewModel统一使用
registerFactory
,请遵循此规则。

팩토리 메서드가 필요한 경우

需要工厂方法的情况

생성자 하나로 끝나지 않고 팩토리 메서드를 호출해야 한다면 람다 폼을 쓴다.
dart
getIt.registerSingleton<HttpClient>(HttpClientFactory.create(getIt()));
Named 파라미터가 필요한 경우도 동일하게 람다 폼을 사용한다.

若无法通过单一构造函数完成初始化,需调用工厂方法时,使用lambda形式。
dart
getIt.registerSingleton<HttpClient>(HttpClientFactory.create(getIt()));
需要命名参数时,同样使用lambda形式。

체크리스트 — 새 feature 추가 시 DI 등록

检查清单 — 添加新功能模块时的DI注册

  • DataSource 인터페이스가
    domain
    또는
    data/data_source/
    에 정의되어 있다
  • 구현체를
    registerSingleton<Interface>
    로 등록 (인터페이스 타입 명시)
  • Repository 인터페이스/구현 등록
  • UseCase 등록 (상태 없으면 싱글톤)
  • ViewModel은 항상
    registerFactory
  • Root 위젯에서만
    getIt<ViewModelType>()
    로 꺼냄

  • DataSource接口已定义在
    domain
    data/data_source/
    目录下
  • 实现类已通过
    registerSingleton<Interface>
    注册(显式指定接口类型)
  • 已完成Repository接口/实现的注册
  • 已完成UseCase注册(无状态则使用单例)
  • ViewModel**始终使用
    registerFactory
    **注册
  • 仅在Root组件中通过
    getIt<ViewModelType>()
    获取实例

안티 패턴

反模式

  • getIt<T>()
    를 Screen 위젯, 공용 위젯, StatelessWidget 트리 깊숙이에서 호출 → 테스트와 프리뷰가 불가능해진다.
  • ❌ ViewModel을
    registerSingleton
    으로 등록 → 화면 재진입 시 상태가 오염된다.
  • diSetup()
    밖에서
    getIt.register*
    를 몰래 호출 → 등록 순서와 의존 그래프가 흩어진다.
  • ❌ 구현 클래스 타입으로
    getIt
    에서 꺼내기 (
    getIt<MockRecipeRepositoryImpl>()
    ) → 인터페이스 타입으로 꺼내 구현 교체가 가능하게 하라.
  • ❌ 在Screen组件、公共组件、StatelessWidget树的深层调用
    getIt<T>()
    → 会导致无法测试和预览。
  • ❌ 使用
    registerSingleton
    注册ViewModel → 重新进入页面时状态会被污染。
  • ❌ 在
    diSetup()
    之外偷偷调用
    getIt.register*
    → 会导致注册顺序和依赖图混乱。
  • ❌ 通过实现类类型从
    getIt
    获取实例(
    getIt<MockRecipeRepositoryImpl>()
    ) → 应通过接口类型获取,确保可替换实现类。