flutter-di-get-it について
Flutter プロジェクト向けの get_it 依存注入ガイドです。単一の登録入口で依存順にセットアップし、Root ウィジェットから ViewModel を取得する規則を示します。画面間で ViewModel を共有しないようファクトリー登録を用います。
主な機能
- 01依存登録の入口を一元化
- 02依存順にオブジェクトを登録
- 03シングルトンとファクトリー登録の使い分け
- 04Root ウィジェットだけで ViewModel を取得
- 05インターフェース型による依存注入
活用シーン
- 01Flutter アプリの依存注入を設定
- 02データソース、Repository、UseCase を登録
- 03画面を開くたびに新しい ViewModel を生成
- 04Screen ウィジェットのテストやプレビューを容易にする
インストール
コマンドをコピーしてターミナルで実行します。
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ウィジェットでのみ
getIt<T>()を使ってViewModelを取得します。Screenウィジェットや共通ウィジェットでは、絶対に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(シングルトン)
状態を持たない純粋な機能なので、シングルトンで十分です。型パラメーターは省略できます(実装クラスが1つだけなので、Dartが推論します)。
getIt.registerSingleton(
GetSavedRecipesUseCase(
recipeRepository: getIt(),
bookmarkRepository: getIt(),
),
);
ViewModel(ファクトリー)
必ず registerFactory を使います。 画面を再度開いたときに、新しいインスタンスが必要になるためです。
getIt.registerFactory<IngredientViewModel>(
() => IngredientViewModel(
ingredientRepository: getIt(),
procedureRepository: getIt(),
getDishesByCategoryUseCase: getIt(),
clipboardService: getIt(),
),
);
Rootウィジェットでの注入
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,
),
);
}
}
原則:getIt<>() の呼び出しは、*Root ウィジェットの build() 内でのみ行います。Screenウィジェットは state と onAction のみを受け取るようにし、プレビューやテストができる状態を保ちます。
シングルトンとファクトリー
| 登録関数 | 使用する場面 | 例 |
|---|---|---|
registerSingleton<T>(instance) | アプリのライフサイクルを通じて1つのインスタンスのみ必要な場合。生成済みのオブジェクトをそのまま渡す場合 | Repository、DataSource、UseCase、HttpClient |
registerLazySingleton<T>(() => ...) | 初回アクセス時まで生成を遅らせたい場合 | 初期化コストの高いオブジェクト |
registerFactory<T>(() => ...) | 呼び出すたびに新しいインスタンスが必要な場合 | ViewModel |
このプロジェクトの実例(lib/core/di/di_setup.dart)では、Repository/DataSource/UseCaseには registerSingleton、ViewModelには registerFactory を使い、一貫して分けています。このルールに従ってください。
ファクトリーメソッドが必要な場合
コンストラクターだけでは完結せず、ファクトリーメソッドを呼び出す必要がある場合は、ラムダ形式を使います。
getIt.registerSingleton<HttpClient>(HttpClientFactory.create(getIt()));
名前付きパラメーターが必要な場合も、同様にラムダ形式を使います。
チェックリスト — 新しいfeatureを追加する際のDI登録
- DataSourceインターフェースが
domainまたはdata/data_source/に定義されている - 実装クラスを
registerSingleton<Interface>で登録する(インターフェース型を明示) - Repositoryのインターフェースと実装を登録する
- UseCaseを登録する(状態を持たない場合はシングルトン)
- ViewModelは必ず
registerFactory - Rootウィジェットでのみ
getIt<ViewModelType>()を使って取得する
アンチパターン
- ❌ Screenウィジェット、共通ウィジェット、またはStatelessWidgetのツリーの深い位置で
getIt<T>()を呼び出す → テストやプレビューができなくなります。 - ❌ ViewModelを
registerSingletonで登録する → 画面に再度入ったときに状態が汚染されます。 - ❌
diSetup()の外でgetIt.register*をひそかに呼び出す → 登録順序と依存関係グラフが分散します。 - ❌ 実装クラス型で
getItから取得する(getIt<MockRecipeRepositoryImpl>())→ インターフェース型で取得し、実装を置き換えられるようにしてください。