Jetpack Compose + MVVM + Hilt で高田健志の伝説アプリを作りたい

はじめまして。
ひっそり未来大でM1やってます。ゆめと申します([Twitter](https://twitter.com/YkOxc), [Instagram](https://www.instagram.com/yume.223/))。
本記事は、[FUN Advent Calendar 2022 part2](https://adventar.org/calendars/7426)の5日目の記事です。

アドカレに便乗して、初めての技術ブログを書きたいなと思い、参加させていただきました。最近触った技術の備忘録を書こうと思います()。

ブログを書くの初めて+Androidよわよわの人なので、不備や間違い等がありましたらご指摘いただけると喜びます。

# 1.はじめに
筆者は最近Jetpack Composeの勉強を始めました。
Composeで〜、MVVMで〜、API叩いて〜、それをRoomに保存して〜、なアプリを作ろうと手を動かしていると、「composeの場合、引数があるViewModelはどう実装するのだろう」と、悩んでしまいました。
色々なサイトを参照したのですが、**みんなDIを使っている**([DIとは](https://qiita.com/iTakahiro/items/353a11f6c9d2a927158d))。
たしかにDI大事だよね。最近流行ってるし。

.
.
.
**DIの学習コストが高い！！！！！！！！**

- ただAPIを叩きたいだけなのに…
- DIしないとviewModelってviewで使えないの？
- DIのこと調べてたら、一生コードが進まない…
- DIナシで書く->導入のリファクタを通じて、DIの恩恵とか、導入方法とか、そういうのを学びたいよ…

ということで、依存性を注入しないで書く->DIして書き直す流れをまとめようと思います。

# 2.作ったもの
とりあえず作ったものが、こちらになります。
[https://github.com/ym223/takadaKenshiDensetsuAndroid](https://github.com/ym223/takadaKenshiDensetsuAndroid)

<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2780537/914e4646-b1d8-3b19-c24e-54d7f0aec0b0.png width=180 />
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2780537/d139b28c-8380-8687-7033-c4d675632fc9.png width=180 />
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2780537/e6da9cad-7228-2e25-9826-622f82c2c2a7.png width=180 />

実装済みの機能は以下になります。
- 伝説を探すボタンをタップ->ランダムに1つ表示
- リスト画面で一覧表示

本記事では、[高田健志の伝説API](https://tools.ic731.net/api/kenshi/)を使用します。

# 3.DIしないで書く
早速ですが、DIしないで書いてみます。

```DensetsuViewModel.kt
class DensetsuViewModel(private val densetsuRepository: DensetsuRepository) : ViewModel() {

    ...

    //DensetsuViewModelクラスのインスタンスを返すためのFactory
    //ここでViewModelに渡す引数を定義する
    class Factory(private val densetsuRepository: DensetsuRepository) : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            @Suppress("UNCHECKED_CAST")
            return DensetsuViewModel(this.densetsuRepository) as T
        }
    }
}
```


```DensetsuScreen.kt
@Composable
fun DensetsuScreen(
    ...
) {
    val repository =
        DensetsuRepository(DensetsuDatabase.getInstance(LocalContext.current).densetsuDao())

    //Factoryを呼び出して、densetsuViewModelを生成
    val densetsuViewModel: DensetsuViewModel = viewModel(
        factory = DensetsuViewModel.Factory(repository)
    )

    ...
}
```

# 4.DIして書いてみる
Hiltを使用します([公式ドキュメント](https://developer.android.com/training/dependency-injection/hilt-android?hl=ja))。

```DensetsuViewModel.kt
@HiltViewModel
class DensetsuViewModel @Inject constructor(
    private val densetsRrepository: DensetsuRepository
    ) : ViewModel() {
    ...
}
```


```DensetsuScreen.kt
@Composable
fun DensetsuScreen(
    ...
    densetsuViewModel: DensetsuViewModel = hiltViewModel()
) {
   ...
}
```
なんだかすごくスッキリしました！！！
`DensetsuViewModel.kt`では、Factoryで引数の定義をしていないし、
`DensetsuScreen.kt`では引数に`densetsuViewModel`を渡してあげているだけです！

# 5.何が起きているのか
DIする前は`DensetsuScreen.kt`で、`DensetsuRepository`インスタンスを生成するために、具体的な値を渡していました。
```diff_kotlin
//DensetsuScreen.kt
@Composable
fun DensetsuScreen(
    ...
+    densetsuViewModel: DensetsuViewModel = hiltViewModel()
) {
-    val repository =
-        DensetsuRepository(DensetsuDatabase.getInstance(LocalContext.current).densetsuDao())

-    //Factoryを呼び出して、densetsuViewModelを生成
-    val densetsuViewModel: DensetsuViewModel = viewModel(
-        factory = DensetsuViewModel.Factory(repository)
-    )

    ...
}
```

DIした後では、`DensetsuViewModel.kt`で、コンストラクタに`DensetsuRepository`インスタンスを渡しています。
```diff_kotlin
//DensetsuViewModel.kt
+ @HiltViewModel
- class DensetsuViewModel(private val densetsuRepository: DensetsuRepository): ViewModel() {
+ class DensetsuViewModel @Inject constructor(
+     private val densetsuRepository: DensetsuRepository
+ ): ViewModel() {

    ...

-    //DensetsuViewModelクラスのインスタンスを返すためのFactory
-    //ここでViewModelに渡す引数を定義する
-    class Factory(private val densetsuRepository: DensetsuRepository) : ViewModelProvider.Factory {
-        override fun <T : ViewModel> create(modelClass: Class<T>): T {
-            @Suppress("UNCHECKED_CAST")
-            return DensetsuViewModel(this.densetsuRepository) as T
-        }
    }
}
```
DI前のように具体的な値を渡していないため、これでは`DensetsuRepository`インスタンスの作り方がわからないような気がします。

ここで、`DensetsuRepository.kt`を見てみます。

```DensetsuRepository.kt
interface DensetsuRepository {
    ...
}

class DensetsuRepositoryImpl @Inject constructor(
    ...
) : DensetsuRepository {
    ...
}
```
`DensetsuRepository`はインターフェースなので、コンストラクタインジェクションができません。Hiltモジュール内に`@Binds`アノテーションを付けた抽象関数を作成することで、インジェクト先にインターフェースの提供方法を教えてあげます。
`@Module`アノテーションを付けることで、Hiltモジュールになります。

```DensetsuRepositoryBindModule.kt
@Module
@InstallIn(SingletonComponent::class)
abstract class DensetsuRepositoryBindModule {

    @Singleton
    @Binds
    abstract fun densetsuRepository(densetsuRepositoryImpl: DensetsuRepositoryImpl): DensetsuRepository
}
```
これで、**`DensetsuRepository`インターフェースを使うときに、`denstsuRepositoryImpl`クラスのインスタンスを渡す**ということをHiltに教えてあげます。

こんな感じで、具体的な値を渡さなくても`DensetsuViewModel`インスタンスを生成することができるようです！

# 6.DIを勉強してみて
規模が小さいアプリなので、正直あまりDIの必要性や恩恵は受けられなかった感はあります。
大規模なチーム開発になったときに、単体テストのしやすさや、機能ごとの開発のしやすさなど、もっとちゃんと恩恵を実感できるのかなという感じがしました。

また、コード書いているときはよくわからないまま書いていたりもしたのですが、今この記事を書きながら、めちゃめちゃ理解が深まった気がします。ちょっとだけ。

とにかく、**実績解除：DI触ったことある**！！！
まだまだ理解しきれないことも多いので、触りながら学んでいこうと思います。

# 参考
[Android Developers:Hilt を使用した依存関係の注入](https://developer.android.com/training/dependency-injection/hilt-android?hl=ja)
[DI（依存性の注入）とは依存性を注入するということである、、？](https://qiita.com/iTakahiro/items/353a11f6c9d2a927158d)
[Android - Jetpack Compose - Room - Retrofit - MVVM - Hilt - Pruebas unitarias - REST API - SQLite](https://www.youtube.com/watch?v=Td4wHh_ZivE&ab_channel=YayoArellano)
# 最後に
考えながら書いてたら、めちゃ重な記事になってしまいました。
今日はもう終わりかけです。大遅刻です。すみません。

明日(12/6)は、Yama-yeahさんと、moriさんです！
どんなお話なんでしょうか！わくわく！