Hiltってなんだ

## Hiltってなんだ

- DIをするためのライブラリ
- [DIについての説明は省略](https://developer.android.com/training/dependency-injection?hl=ja&_gl=1*141hquf*_up*MQ..*_ga*ODk2ODA0MDgzLjE3NDE3NzMxMTg.*_ga_6HH9YJMN9M*MTc0MTc3MzExNy4xLjAuMTc0MTc3MzExNy4wLjAuMTIxOTYyNDE4NA..)
    - DI ≠ (DIP)依存性逆転の原則

## Hiltの導入方法(ざっくり)

1. [依存関係の追加](https://developer.android.com/training/dependency-injection/hilt-android?hl=ja)
    - `kapt`ではなく`ksp`にすると良い
2. `MainActivity`に`@AndroidEntryPoint`アノテーションを付ける
    
    ```kotlin
    @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)
                    }
                }
            }
        }
    }
    ```
    
3. `MainApplication.kt`を作成し、`@HiltAndroidApp` というアノテーションを付ける
    
    ```kotlin
    @HiltAndroidApp
    class MainApplication: Application()
    ```
    
4. Androidマニフェストにそのことを記述する
    
    ```kotlin
    <?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の基本的な使い所4つ

1. **ViewModelとCompose(Fragment/Activity)のDI**
    - `@HiltViewModel` アノテーションをViewModelにつける
    - Composeではコンストラクタに`viewModel: HogeViewModel = hiltViewModel()` を入れる
        - 要`hilt-navigation-compose`
    - 依存関係を自動で注入してくれる
    
    ```kotlin
    @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() {}
    ```
    
2. **ViewModelとRepository(UseCase)のDI**
    - ViewModelのコンストラクタに `@Inject` アノテーションを付ける
    - Repository(UseCase)をコンストラクタ引数として渡す
    - HiltがRepositoryのインスタンスを生成し、ViewModelに注入
    
    ```kotlin
    @HiltViewModel
    class HomeScreenViewModel @Inject constructor(
        private val repository: CharacterRepository,
    ) : ViewModel() {
        private val _viewState = MutableStateFlow<HomeScreenViewState>(HomeScreenViewState.Loading)
        val viewState: StateFlow<HomeScreenViewState> = _viewState.asStateFlow()
    
        init {
            loadCharacters()
        }
    
        private fun loadCharacters() {
            viewModelScope.launch {
                try {
                    val pagingSource = repository.getCharacterPagingSource()
                    _viewState.update {
                        HomeScreenViewState.GridDisplay(
                            characters = Pager(
                                config = PagingConfig(enablePlaceholders = false, pageSize = 20),
                                pagingSourceFactory = { pagingSource }
                            ).flow.cachedIn(viewModelScope)
                        )
                    }
                } catch (e: Exception) {
                    _viewState.update { HomeScreenViewState.Error(e.message ?: "Unknown error") }
                }
            }
        }
    }
    ```
    
3. **RepositoryインターフェイスとRepositoryImpl(実装クラス)のDI (RepositoryModule)**
    - `@Binds` アノテーションを使用。
    - `@Module` と `@InstallIn` アノテーションが付いた抽象クラス内で、`@Binds` アノテーションを付けた抽象メソッドを定義。
    - メソッドの引数で実装クラスを指定し、戻り値でインターフェースを指定。
    - `@Provides` よりも `@Binds` の方が効率的（インスタンス生成が不要なため）。
    - `bindHoge` という名前をつけがち
    
    ```kotlin
    @Module
    @InstallIn(SingletonComponent::class)
    abstract class RepositoryModule {
    
        @Singleton
        @Binds
        abstract fun bindRepository(
            repositoryImpl: CharacterRepositoryImpl,
        ): CharacterRepository
    }
    ```
    
4. **APiClient(`Retrofit`や`Ktor`など)のDI(NetworkModule)**
    - `@Provides` アノテーションを使用。
    - `@Module` と `@InstallIn` アノテーションが付いたクラス内で、`@Provides` アノテーションを付けたメソッドを定義。
    - メソッド内で`APIClient`のインスタンスを生成して返す。
    - 必要に応じて、`OkHttpClient`などの設定も行う。
    - Singletonスコープにすることが多い。
    - `provideHoge`という名前をつけがち

```kotlin
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    @Singleton
    fun provideHttpClient(): HttpClient {
        return HttpClient(OkHttp) {
            defaultRequest { url(BASE_URL) }

            install(Logging) {
                logger = Logger.SIMPLE
            }

            install(ContentNegotiation) {
                json(Json {
                    ignoreUnknownKeys = true
                })
            }
        }
    }

    @Provides
    @Singleton
    fun provideKtorClient(httpClient: HttpClient): KtorClient {
        return KtorClient(httpClient)
    }
}

// Retrofitの例
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            // ... OkHttpClientの設定 ...
            .build()
    }

    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://example.com/")
            .client(okHttpClient) // Dagger/HiltがOkHttpClientを自動的に注入
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}
```

## 使ってみたメリット

1. NavHostがスッキリ書けるため嬉しい

```kotlin
@Composable
fun MyAppNavHost(
    navController: NavHostController = rememberNavController(),
) {
    NavHost(
        navController = navController,
        startDestination = HomeScreenRoute
    ) {
        homeScreen()
        characterDetailsScreen()
        characterEpisodeScreen()
    }
}
```

1. ボイラープレートコードを記述しなくて良い
    - ViewModel Factoryとかその辺を書かなくて良くなる

## やってて詰まった点(アノテーションの意味と使い所)

**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

```kotlin
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            // ... OkHttpClientの設定 ...
            .build()
    }

    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { // OkHttpClientはHiltが自動的に注入
        return 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 アノテーションが付いた抽象クラス内で使用。 |

## 参考文献

[Hilt を使用した依存関係挿入  |  Android Developers](https://developer.android.com/training/dependency-injection/hilt-android?hl=ja)