Hiltってなんだ
対象読者
- Kotlinの基本文法がわかる人
- DIの概念をなんとなく知っている人
- Hiltを初めて使う or 使い始めたばかりの人
Hiltとは
- DIをするためのライブラリ
- DIについての説明は省略
-
DI ≠ DIP(似てるけど別物)
- DI(Dependency Injection): 依存性を外から注入する「仕組み・手法」
- DIP(Dependency Inversion Principle): 依存性逆転の原則。「具体ではなく抽象に依存すべき」という「設計の考え方」
- HiltはDIを行うライブラリだが、DIPの実現にも役立つ
用語の整理
同じ意味でも複数の言い方があるので整理:
| 日本語 | 英語 / カタカナ | 意味 |
|---|---|---|
| 依存 / 依存性 / 依存関係 | Dependency | あるクラスが動くために必要なオブジェクト |
| 依存性の注入 | DI / Dependency Injection | 外から依存を渡す仕組み |
| 依存性逆転の原則 | DIP / Dependency Inversion Principle | 具体ではなく抽象に依存する設計原則 |
| 注入する | Inject / インジェクト | 依存を渡す動作 |
| コンストラクタ注入 | Constructor Injection / コンストラクタインジェクション | コンストラクタ引数で依存を渡す方法(推奨) |
| フィールド注入 | Field Injection / フィールドインジェクション | フィールドに直接依存を渡す方法 |
| スコープ | Scope | インスタンスの生存期間 |
| バインド | Bind | インターフェースと実装を紐付ける |
| プロバイド | Provide | 依存オブジェクトを提供する |
AndroidのDIライブラリ
| ライブラリ | 特徴 |
|---|---|
| Hilt | Google公式。Daggerベース。KSPでコード生成。Androidに特化 |
| Koin | 軽量でシンプル。KMP対応。ランタイムで依存解決(コード生成なし) |
| Metro | 新しめ。KMP対応。DroidKaigi 2025で採用 |
- Android専用プロジェクト → Hiltが推奨(Google公式)
- KMP(Kotlin Multiplatform) → Koinがよく使われる(iOSでも動く)
このドキュメントでは、Android開発で最も使われているHiltについて解説します。
Hiltがない世界の辛さ
Hiltを使わずに手動でオブジェクトを生成・受け渡しするとどうなるか見てみましょう。
手動DIのコード例
// Hiltなしの世界
// 1. Application で ApiClient を作る
class MyApplication : Application() {
val apiClient = ApiClient() // ここで生成
}
// 2. Activity で Repository を作る
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Applicationからapiを取得して...
val app = application as MyApplication
val repository = UserRepository(app.apiClient)
// ViewModelはどうする?
val viewModel = UserViewModel(repository) // これはNG!
setContent {
UserScreen(viewModel) // 渡すしかない
}
}
}
問題1: ViewModelを直接newできない
ViewModelはViewModelProvider経由でないとライフサイクル管理されない。画面回転で状態が消える。
// じゃあViewModelProvider使おう...でも引数付きViewModelは?
class UserViewModel(
private val repository: UserRepository // これを渡したい
) : ViewModel()
// Factory を自分で書く必要がある!
class UserViewModelFactory(
private val repository: UserRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return UserViewModel(repository) as T
}
}
// Activity内で使う
val factory = UserViewModelFactory(repository)
val viewModel = ViewModelProvider(this, factory)[UserViewModel::class.java]
問題2: ViewModelごとにFactoryを書く地獄
10個のViewModelがあれば10個のFactory。引数が変わるたびに修正が必要。
問題3: 依存の連鎖が爆発する
ViewModel → UseCase → Repository → ApiClient → OkHttp → ...
全部手動で繋ぐとActivityが肥大化して管理不能に。
問題4: テストできない
val apiClient = ApiClient()がハードコードされていたら、テスト時にモックに差し替えられない。
手動DI vs Hilt 比較表
| 手動DIの問題 | Hiltを使うと |
|---|---|
| Activity/Applicationが依存解決の責務を持つ | 「欲しいものを宣言」すれば勝手に届く |
| ViewModelのライフサイクル管理が複雑 | ライフサイクルは自動管理 |
| テスト時の差し替えが困難 | 簡単に差し替え可能 |
| 依存が増えるたびにボイラープレート増殖 | 依存が増えてもコード変更最小限 |
Hiltの導入方法(ざっくり)
-
-
kaptではなくkspにすると良い(ビルドが早くなるため)
-
-
MainActivityに@AndroidEntryPointアノテーションを付ける@AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { Multi_module_test_theme { val navController = rememberNavController() CompositionLocalProvider(LocalNavController provides navController) { MyAppNavHost(navController) } } } } } -
MainApplication.ktを作成し、@HiltAndroidAppというアノテーションを付ける@HiltAndroidApp class MainApplication: Application() -
Androidマニフェストにそのことを記述する
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Multi_module_test" <!-- 以下を追加 --> android:name=".MainApplication" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true" android:label="@string/app_name" android:theme="@style/Theme.Multi_module_test"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
インジェクションの種類
Hiltでは2種類のインジェクション方法があります。
コンストラクタインジェクション(推奨)
クラスのコンストラクタに@Injectを付けて依存を受け取る方法。こちらが推奨。
// ViewModel、RepositoryImpl、UseCaseなど、自分でインスタンス化できるクラス
class UserRepositoryImpl @Inject constructor(
private val apiClient: ApiClient,
private val database: AppDatabase,
) : UserRepository {
// ...
}
@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository, // インターフェースに依存
) : ViewModel() {
// ...
}
メリット:
- 依存関係が明確(コンストラクタを見ればわかる)
- イミュータブル(
valで受け取れる) - テストしやすい(コンストラクタに直接モックを渡せる)
-
使用側が依存を気にしなくて済む
- 例:ComposableはViewModelの依存(Repository等)を知らなくていい
-
hiltViewModel()を呼ぶだけで、Hiltが裏で全部解決してくれる
フィールドインジェクション(仕方ない場合のみ)
フィールドに@Injectを付けて依存を受け取る方法。Androidフレームワークがインスタンスを生成するクラスでのみ使用。
// Activity、Fragment、Serviceなど、Androidが生成するクラス
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject
lateinit var analyticsHelper: AnalyticsHelper // フィールドインジェクション
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// この時点でanalyticsHelperは注入済み
}
}
なぜフィールドインジェクションが必要?
- Activity/Fragmentのコンストラクタはシステムが呼び出すため、引数を追加できない
-
@AndroidEntryPointを付けると、HiltがonCreate()の前に自動でフィールドに注入してくれる
注意点:
-
lateinit varにする必要がある(イミュータブルにできない) -
privateにできない(Hiltがアクセスする必要があるため)
どちらを使うべき?
| ケース | 使うべき方法 |
|---|---|
| ViewModel、Repository、UseCase | コンストラクタインジェクション |
| Activity、Fragment、Service | フィールドインジェクション |
| 自分でnewできるクラス | コンストラクタインジェクション |
| Androidがnewするクラス | フィールドインジェクション |
Hiltの基本的な使い所4つ
-
ComposeでのViewModel利用
-
@HiltViewModelアノテーションをViewModelにつける - Composeではコンストラクタに
viewModel: HogeViewModel = hiltViewModel()を入れる- 要
hilt-navigation-compose
- 要
- 依存関係を自動で注入してくれる
@Composable fun HomeScreen( viewModel: HomeScreenViewModel = hiltViewModel(), ) { val viewState by viewModel.viewState.collectAsStateWithLifecycle() when (val state = viewState) { is HomeScreenViewState.Loading -> { LoadingState() } is HomeScreenViewState.GridDisplay -> { Log.d("HomeScreen", "viewState in GridDisplay: $state") val characters = state.characters.collectAsLazyPagingItems() CharacterGrid(characters = characters) } is HomeScreenViewState.Error -> { val errorMessage = state.errorMessage Text(text = "Error: $errorMessage") } } } @HiltViewModel class HomeScreenViewModel @Inject constructor(): ViewModel() {} -
-
ViewModelとRepository(UseCase)のDI
- ViewModelのコンストラクタに
@Injectアノテーションを付ける - Repository(UseCase)をコンストラクタ引数として渡す
- HiltがRepositoryのインスタンスを生成し、ViewModelに注入
@HiltViewModel class HomeScreenViewModel @Inject constructor( private val repository: CharacterRepository, ) : ViewModel() { private val _viewState = MutableStateFlow<HomeScreenViewState>(HomeScreenViewState.Loading) val viewState = _viewState.asStateFlow() init { loadCharacters() } private fun loadCharacters() = viewModelScope.launch { repository.getCharacterPagingSource().fold( onSuccess = { pagingSource -> _viewState.value = HomeScreenViewState.GridDisplay( characters = Pager( config = PagingConfig(enablePlaceholders = false, pageSize = 20), pagingSourceFactory = { pagingSource } ).flow.cachedIn(viewModelScope) ) }, onFailure = { e -> _viewState.value = HomeScreenViewState.Error(e.message ?: "Unknown error") } ) } } - ViewModelのコンストラクタに
-
RepositoryとRepositoryImplのDI (RepositoryModule)
DIP(依存性逆転の原則)とは
一言で言うと: 「具体的なもの」ではなく「約束(インターフェース)」に頼ろう、という考え方。
身近な例で説明:
スマホの充電を想像してください。USB Type-Cという「規格」に従っていれば、
どのメーカーのケーブルでも充電できます。もしスマホが「特定メーカーのケーブル専用」だったら、
そのケーブルが壊れたら終わりです。-
悪い例(DIPに違反): ViewModelがRepositoryImpl(具体的な実装)に直接依存
→ RepositoryImplを変えるたびにViewModelも修正が必要 -
良い例(DIPに準拠): ViewModelがRepository(インターフェース)に依存
→ 実装を差し替えてもViewModelは変更不要。テスト用のモック実装も簡単に注入できる
Hiltでの実現:
@Bindsを使ってインターフェースと実装クラスを紐付けるRepository + RepositoryImpl + RepositoryModuleの関係性
❌ 悪い例(DIPに違反)
✅ 良い例(DIPに準拠)
コンポーネント 役割 例えるなら Repository 「何ができるか」を定義するインターフェース USB Type-Cという規格 RepositoryImpl 実際の処理を行う実装クラス 実際のType-Cケーブル RepositoryModule HiltにRepositoryとRepositoryImplの紐付けを教える 「このスマホにはこのケーブルを使う」という設定 実装例
1. Repository(インターフェース定義)
interface CharacterRepository { suspend fun getCharacterPagingSource(): Result<CharacterPagingSource> suspend fun getCharacterById(id: Int): Result<Character> }2. RepositoryImpl(実装クラス)
class CharacterRepositoryImpl @Inject constructor( private val ktorClient: KtorClient, ) : CharacterRepository { override suspend fun getCharacterPagingSource(): Result<CharacterPagingSource> = runCatching { CharacterPagingSource(ktorClient) } override suspend fun getCharacterById(id: Int): Result<Character> = runCatching { ktorClient.getCharacter(id) } }3. RepositoryModule(バインディング)
-
@Bindsアノテーションを使用 -
@Moduleと@InstallInアノテーションが付いた抽象クラス内で、@Bindsアノテーションを付けた抽象メソッドを定義 - メソッドの引数で実装クラスを指定し、戻り値でインターフェースを指定
-
@Providesよりも@Bindsの方が効率的(インスタンス生成が不要なため) -
bindHogeという名前をつけがち
@Module @InstallIn(SingletonComponent::class) abstract class RepositoryModule { @Singleton @Binds abstract fun bindRepository( repositoryImpl: CharacterRepositoryImpl, ): CharacterRepository } -
悪い例(DIPに違反): ViewModelがRepositoryImpl(具体的な実装)に直接依存
-
APIClient(
RetrofitやKtorなど)のDI (NetworkModule)-
@Providesアノテーションを使用。 -
@Moduleと@InstallInアノテーションが付いたクラス内で、@Providesアノテーションを付けたメソッドを定義。 - メソッド内で
APIClientのインスタンスを生成して返す。 - 必要に応じて、
OkHttpClientなどの設定も行う。 - Singletonスコープにすることが多い。
-
provideHogeという名前をつけがち
-
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideHttpClient(): HttpClient =
HttpClient(OkHttp) {
defaultRequest { url(BASE_URL) }
install(Logging) {
logger = Logger.SIMPLE
}
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
})
}
}
@Provides
@Singleton
fun provideKtorClient(httpClient: HttpClient): KtorClient =
KtorClient(httpClient)
}
// Retrofitの例
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient =
OkHttpClient.Builder()
// ... OkHttpClientの設定 ...
.build()
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit =
Retrofit.Builder()
.baseUrl("https://example.com/")
.client(okHttpClient) // Dagger/HiltがOkHttpClientを自動的に注入
.addConverterFactory(GsonConverterFactory.create())
.build()
}
使ってみたメリット
-
ボイラープレートコードを書かなくて良い
- ViewModel Factoryを自分で書く必要がない
-
hiltViewModel()を呼ぶだけでViewModelが取得できる
-
依存の変更に強い
- ViewModelの依存が増えても、呼び出し側のコードは変更不要
- 例:
UserViewModelに新しい依存を追加しても、Compose側はhiltViewModel()のまま
-
テストしやすい
- インターフェースに依存しているので、テスト用のFake実装に差し替えやすい
-
Navigation Composeとの相性が良い
- Nav2(Navigation Compose)でもNav3でも、画面ごとにViewModelを簡単に取得できる
- 各画面で
hiltViewModel()を呼ぶだけ
スコープ管理
スコープとは?
スコープ = インスタンスの生存期間
同じスコープ内では同じインスタンスが再利用される。スコープが違えば別のインスタンスが生成される。
Hiltの主要なスコープ
| スコープ | Component | 生存期間 | 主な用途 |
|---|---|---|---|
@Singleton |
SingletonComponent | アプリ全体 | APIクライアント、DB |
@ActivityScoped |
ActivityComponent | Activity | Activity固有の状態 |
@ViewModelScoped |
ViewModelComponent | ViewModel | ViewModel内で共有したい依存 |
@FragmentScoped |
FragmentComponent | Fragment | Fragment固有の状態 |
スコープの図解
使い分けの目安
// アプリ全体で1つでいい → @Singleton
@Singleton
@Provides
fun provideRetrofit(): Retrofit = ...
// ViewModelごとに持ちたい → @ViewModelScoped
@ViewModelScoped
@Provides
fun provideUseCase(): SomeUseCase = ...
// スコープなし → 毎回新しいインスタンス
@Provides
fun provideFormatter(): DateFormatter = DateFormatter()
実務での使い分け
結論:ほとんどの場合 @Singleton で十分
| スコープ | 実務での使用頻度 | コメント |
|---|---|---|
@Singleton |
⭐⭐⭐ よく使う | Repository, APIクライアント, DBなど |
@ViewModelScoped |
⭐ たまに使う | ViewModel内で状態を共有したい特殊ケース |
@ActivityScoped |
ほぼ使わない | Composeメインだと出番なし |
@FragmentScoped |
ほぼ使わない | Fragmentを使わないなら不要 |
@ViewModelScoped を使う場面:
// 複数のUseCaseが同じ状態を共有したい場合
@ViewModelScoped
class ScreenStateHolder @Inject constructor() {
var currentPage = 0
}
@HiltViewModel
class MyViewModel @Inject constructor(
private val loadUseCase: LoadUseCase, // 同じStateHolderを参照
private val refreshUseCase: RefreshUseCase, // 同じStateHolderを参照
) : ViewModel()
迷ったら @Singleton でOK。 問題が起きてから細かく分ければいい。
よくある間違い
// ❌ NG: ViewModelScopedなのにSingletonComponentにインストール
@Module
@InstallIn(SingletonComponent::class) // ← ここが間違い
object MyModule {
@ViewModelScoped // ← SingletonComponentでは使えない
@Provides
fun provideUseCase(): UseCase = ...
}
// ✅ OK: ViewModelScopedはViewModelComponentに
@Module
@InstallIn(ViewModelComponent::class) // ← 正しい
object MyModule {
@ViewModelScoped
@Provides
fun provideUseCase(): UseCase = ...
}
ポイント: スコープアノテーションと@InstallInのComponentは対応させる必要がある
| スコープ | 対応するComponent |
|---|---|
@Singleton |
SingletonComponent |
@ActivityScoped |
ActivityComponent |
@ViewModelScoped |
ViewModelComponent |
@FragmentScoped |
FragmentComponent |
テストでの活用
Hiltがテストを楽にする理由
DIPに従って設計されていれば、本番用の実装をテスト用に差し替えられる。
本番環境:
ViewModel → Repository(インターフェース)→ RepositoryImpl → 実際のAPI
テスト環境:
ViewModel → Repository(インターフェース)→ FakeRepository → モックデータ
テスト用の設定
1. 依存関係の追加
// build.gradle.kts
dependencies {
testImplementation("com.google.dagger:hilt-android-testing:2.x.x")
kspTest("com.google.dagger:hilt-android-compiler:2.x.x")
androidTestImplementation("com.google.dagger:hilt-android-testing:2.x.x")
kspAndroidTest("com.google.dagger:hilt-android-compiler:2.x.x")
}
2. テストクラスの書き方
@HiltAndroidTest // Hiltのテストであることを宣言
class UserViewModelTest {
@get:Rule
val hiltRule = HiltAndroidRule(this) // Hiltのセットアップ
@Inject
lateinit var viewModel: UserViewModel
@Before
fun setup() {
hiltRule.inject() // 依存を注入
}
@Test
fun `ユーザー取得が成功する`() {
// viewModelを使ったテスト
}
}
3. テスト用の実装に差し替える
// 本番用のモジュールを無効化して、テスト用に差し替え
@HiltAndroidTest
@UninstallModules(RepositoryModule::class) // 本番用を無効化
class UserViewModelTest {
@Module
@InstallIn(SingletonComponent::class)
object TestRepositoryModule {
@Singleton
@Provides
fun provideRepository(): UserRepository = FakeUserRepository()
}
// ...
}
FakeRepositoryの例
class FakeUserRepository : UserRepository {
private val fakeUsers = mutableListOf(
User(id = 1, name = "テスト太郎"),
User(id = 2, name = "テスト花子"),
)
override suspend fun getUsers(): List<User> = fakeUsers
override suspend fun getUserById(id: Int): User? =
fakeUsers.find { it.id == id }
// テスト用にデータを操作するメソッドも追加できる
fun addUser(user: User) {
fakeUsers.add(user)
}
}
テストが楽になるポイント
| Hiltなし | Hiltあり |
|---|---|
| 手動でFakeを注入する必要がある |
@UninstallModulesで簡単に差し替え |
| 依存の連鎖を全部自分で構築 | Hiltが自動で解決 |
| テストごとにセットアップが複雑 |
@HiltAndroidTestで統一 |
やってて詰まった点(アノテーションの意味と使い所)
1. @Module
-
意味:
- このクラスがDagger/Hiltのモジュールであることを示します。
- モジュールは、依存オブジェクトの提供方法を定義する場所です。
- Hiltは、
@Moduleアノテーションが付いたクラスを見つけて、そこから依存関係の情報を取得します。
-
使いどころ:
- 依存オブジェクトの生成ロジック(
@Providesメソッド)や、インターフェースと実装クラスのバインディング(@Bindsメソッド)を記述するクラスに付けます。 - 通常、
objectまたはabstract classとして定義します。-
object:@Providesメソッドのみを含むモジュールに適しています。 -
abstract class:@Bindsメソッドを含むモジュール、または@Bindsと@Providesを組み合わせたモジュールに適しています。
-
- 依存オブジェクトの生成ロジック(
2. @Provides
-
意味:
- このメソッドが依存オブジェクトを提供することを示します。
-
@Providesメソッド内で、依存オブジェクトを生成し、returnします。 - メソッドの戻り値の型が、提供される依存オブジェクトの型になります。
- メソッドの引数には、依存オブジェクトの生成に必要な他の依存オブジェクトを指定できます(Dagger/Hiltが自動的に注入します)。
-
使いどころ:
- 外部ライブラリのインスタンス(Retrofit, OkHttpClient, Room Databaseなど)を提供する。
- 複雑な初期化ロジックが必要なオブジェクトを提供する。
- インターフェースに対して複数の実装が存在し、条件によって使い分けたい場合。
-
@Moduleアノテーションが付いたクラス(objectまたはclass)内に記述します。
Kotlin
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient =
OkHttpClient.Builder()
// ... OkHttpClientの設定 ...
.build()
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit = // OkHttpClientはHiltが自動的に注入
Retrofit.Builder()
.baseUrl("https://example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
3. @Singleton
-
意味:
- このアノテーションが付いた依存オブジェクトが、アプリケーション全体で1つだけ生成され、共有されることを示します(Singletonスコープ)。
-
@Providesメソッドや、@Injectが付いたコンストラクタに付けることができます。
-
使いどころ:
- アプリケーション全体で同じインスタンスを使い回したいオブジェクト(データベース接続、ネットワーククライアント、設定オブジェクトなど)に付けます。
4. @InstallIn(Component::class)
-
意味 (Hilt専用):
-
@Moduleアノテーションと一緒に使用します。 - このモジュールが、どのコンポーネントにインストールされるか(どのスコープで有効になるか)を指定します。
-
Component::classには、Hiltが提供する定義済みのコンポーネントクラスを指定します。-
SingletonComponent::class: アプリケーション全体 (Singleton) -
ActivityComponent::class: Activity -
FragmentComponent::class: Fragment -
ViewModelComponent::class: ViewModel -
ServiceComponent::class: Service -
ViewComponent::class: View (カスタムViewなど) -
ViewWithFragmentComponent::class: Fragment内のView
-
- どのコンポーネントにインストールするかによって、提供される依存オブジェクトのスコープ(生存期間)が決まります。
-
-
使いどころ:
-
@Moduleアノテーションが付いたクラスに必ず付けます。
-
5. @Binds
-
意味:
- インターフェースとその実装クラスを関連付ける(バインドする)ために使用
- 抽象メソッドに
@Bindsアノテーションを付け、戻り値の型をインターフェース、引数の型を実装クラスにする - Dagger/Hiltは、
@Bindsメソッドの情報を元に、インターフェースが要求された場合に、どの実装クラスのインスタンスを提供すればよいかを判断 -
@Bindsメソッドは抽象メソッドなので、実装はDagger/Hiltが自動生成
-
使いどころ:
- インターフェースと実装クラスが1対1で対応する場合に使用
-
@Providesを使うよりも@Bindsを使う方が効率的(インスタンス生成のオーバーヘッドがないため) -
@Moduleアノテーションが付いた抽象クラス内で使用
| アノテーション | 役割 | 使いどころ |
|---|---|---|
| @Module | このクラスがDagger/Hiltのモジュールであることを示す。依存オブジェクトの提供方法を定義する場所。 | 依存オブジェクトの生成ロジック(@Provides)やバインディング(@Binds)を記述するクラスに付ける。通常は object (Kotlin) または abstract class。HogeModuleみたいな名前のファイル。APIクライアントやRepositoryのDIのときに用いる |
| @Provides | このメソッドが依存オブジェクトを提供することを示す。メソッド内で依存オブジェクトを生成して return する。 | 外部ライブラリのインスタンスや、複雑な初期化が必要なオブジェクトの提供、インターフェースに対して複数の実装が存在し条件によって使い分けたい場合に利用。@Module が付いたクラス内に記述。 |
| @Singleton | このアノテーションが付いた依存オブジェクトが、アプリケーション全体で1つだけ生成され、共有されることを示す(Singletonスコープ)。 | アプリケーション全体で同じインスタンスを使い回したいオブジェクト(データベース接続、ネットワーククライアントなど)に付ける。@Provides メソッドや、@Inject が付いたコンストラクタに付ける。 |
| @InstallIn(Component::class) | (Hilt専用) @Moduleと一緒に使用。モジュールをどのコンポーネントにインストールするか(どのスコープで有効にするか)を指定する。SingletonComponent::class はアプリケーション全体で共有されるシングルトンコンポーネントを指定。 | @Module アノテーションが付いたクラスに必ず付ける。 |
| @Binds | インターフェースとその実装クラスを関連付ける。抽象メソッドに @Binds を付け、戻り値の型をインターフェース、引数の型を実装クラスにする。Dagger/Hiltが実装クラスのインスタンスを提供。 | インターフェースと実装クラスが1対1で対応する場合に使用。@Provides よりも効率的。@Module アノテーションが付いた抽象クラス内で使用。 |