SuperAPI AI Directory

flutter-di-get-it

Data & analytics

View repository

Updated 10-15 21:53

About this skill

Flutter DI — get_it

Principles

  • DI is registered in a single entry lib/core/di/di_setup.dart 의 diSetup() function. Do not split it up per feature (since this is a single-package structure).
  • main() in runApp() calling before diSetup() must be called.
  • Only in the Root widget, retrieve the ViewModel with getIt<T>() . Never use getIt in Screen widgets or shared widgets (to ensure testability).
  • ViewModel은 Always registerFactory registered with. If it is a singleton, multiple screens end up sharing the same state.

Default entry point

// 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());
}

The registration order goes from the leaves of the dependency graph upward. It must be in the order DataSource → Repository → UseCase → ViewModel so that when getIt() references it, it is already registered.


Registration pattern per layer

DataSource (singleton)

getIt.registerSingleton<RecipeDataSource>(RemoteRecipeDataSourceImpl());
getIt.registerSingleton<LocalStorage>(DefaultLocalStorage());

Register by interface type so that the implementation is never referenced directly from the Root/ViewModel.

Repository (singleton)

Constructor parameters are getIt() passed by unpacking with. If generic inference is needed, explicitly write getIt<Foo>() .

getIt.registerSingleton<RecipeRepository>(
  MockRecipeRepositoryImpl(
    recipeDataSource: getIt(),
  ),
);
getIt.registerSingleton<BookmarkRepository>(MockBookmarkRepositoryImpl());

UseCase (singleton)

Since it is a pure function with no state, a singleton is sufficient. Type parameters can be omitted (since there is only one implementation class, Dart can infer it).

getIt.registerSingleton(
  GetSavedRecipesUseCase(
    recipeRepository: getIt(),
    bookmarkRepository: getIt(),
  ),
);

ViewModel (factory)

Always registerFactory use. This is because a new instance is needed when reopening the screen.

getIt.registerFactory<IngredientViewModel>(
  () => IngredientViewModel(
    ingredientRepository: getIt(),
    procedureRepository: getIt(),
    getDishesByCategoryUseCase: getIt(),
    clipboardService: getIt(),
  ),
);

Injecting in the 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,
      ),
    );
  }
}

Principles: getIt<>() Calls are *Root in the widget's build() only within. Keep the Screen widget receiving only state 와 onAction so that previews/tests are possible.


Singleton vs factory

Registration function When to use 예
registerSingleton<T>(instance) When only one instance is needed for the app's lifetime. When putting in an already-created object directly Repository, DataSource, UseCase, HttpClient
registerLazySingleton<T>(() => ...) When you want to delay creation until the first access Objects with high initialization cost
registerFactory<T>(() => ...) New instance on every call ViewModel

Actual examples in this project (lib/core/di/di_setup.dart) have Repository/DataSource/UseCase consistently separated with registerSingleton, ViewModel은 registerFactory . Follow this rule.


When a factory method is needed

If it does not end with a single constructor and you need to call a factory method, use the lambda form.

getIt.registerSingleton<HttpClient>(HttpClientFactory.create(getIt()));

The same lambda form is used when named parameters are needed.


Checklist — DI registration when adding a new feature

  • The DataSource interface is domain or data/data_source/ defined in
  • the implementation in registerSingleton<Interface> Register as (specify interface type)
  • Register Repository interface/implementation
  • Register UseCase (singleton if stateless)
  • ViewModel은 Always registerFactory
  • only in the Root widget getIt<ViewModelType>() retrieve with

Anti-pattern

  • ❌ getIt<T>() call inside Screen widgets, shared widgets, or deep in the StatelessWidget tree → testing and previews become impossible.
  • ❌ ViewModel을 registerSingleton register as → state becomes corrupted when re-entering the screen.
  • ❌ diSetup() outside getIt.register* secretly call → registration order and dependency graph become scattered.
  • ❌ with the implementation class type getIt retrieve from (getIt<MockRecipeRepositoryImpl>()) → retrieve by interface type so the implementation can be swapped.