Dagger Hilt (DevFest 2020 資料)

DevFestの資料の記事版です。ステップごとにサンプルアプリの差分付きで説明します。
なぜDependency Injectionが必要なのか から始め、Dagger Hiltの説明、実践的なプラクティスまで説明していきます！

# Dependency Injection(DI)とはなにか

### なぜDIが必要なのか

DI、ちょっと難しいイメージありますが、そもそもなんで必要なんでしょうか？
作っているのが動画再生するアプリでVideoPlayerというクラスがあるとしましょう。
VideoPlayerのクラスの中にデータベースやcodecなどが**ハードコードされています。**

コード: https://github.com/takahirom/hilt-sample-app/commit/8c36602aaa4e27d8f10c81e2808f0ff452f1c8a4#diff-bbc9d28d8bcbd080a704cacc92b8cf37R19

```kotlin
class VideoPlayer {
    // データベースにビデオの一覧が保存されている (Roomというライブラリを使っている)
    private val database = Room
        .databaseBuilder(
            App.instance,
            VideoDatabase::class.java,
            "database"
        )
        .createFromAsset("videos.db")
        .build()
    // 使えるコーデック一覧
    private val codecs = listOf(FMP4, WebM, MPEG_TS, AV1)

    private var isPlaying = false

    fun play() {
...
    }
}
```

使うときにはこれだけで、十分シンプルに見えます。

```kotlin
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val videoPlayer = VideoPlayer()
        videoPlayer.play()
    }
}
```

しかし、これだけだと開発を進めていく中で以下のような問題が起こります。

* VideoPlayerを再度使いたい場合、データベースやコーデックがハードコードされているので、データベースを変えて再生したりなどができない。
* 実行時やデバッグで交換できない。
* **テストでも交換できない。例えばテストではメモリ内のDB使いたい場合などは困る。**
* さまざまな依存関係がコードの中に入ってくるので**コードが見にくくなり、リファクタリングを難しくする。**

**この問題を簡単に回避するために簡単なDIを試してみましょう！**

# 簡単にDIしてみよう

ここでDependency Injection(DI)です。日本語だと依存性の注入です。
最初は簡単に**コンストラクタインジェクション**というコンストラクタを使って依存性を注入する方法を紹介します。
簡単にVideoPlayerの**コンストラクタに依存関係を渡してあげるだけ**です。
他にもsetterによって注入する方法をsetter injectionといいます。

コンストラクタインジェクションの例
差分: https://github.com/takahirom/hilt-sample-app/commit/a1fdef28515d158577313b90f7c2590bd5905366

**VideoPlayerは依存関係が交換可能で、シンプルになった！**

```kotlin
class VideoPlayer(
    private val database: VideoDatabase,
    private val codecs: List<Codec>
) {
    private var isPlaying = false

    fun play() {
...
    }
}
```

コンストラクタインジェクションを使うと使う側で、**そのクラスの依存関係を先に作ってからでないとインスタンス化することができません。**

```kotlin
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val videoDatabase = Room
            .databaseBuilder(
                this,
                VideoDatabase::class.java,
                "database"
            )
            .createFromAsset("videos.db")
            .build()
        val codecs = listOf(FMP4, WebM, MPEG_TS, AV1)
        val videoPlayer = VideoPlayer(videoDatabase, codecs)
        videoPlayer.play()
    }
}
```

いろんな画面で、VideoPlayerを作るたびにこのようなコードを書く必要があるので、これは **ボイラープレートとなりえます**。
この処理をよく見ると**"VideoPlayerを作るための処理"と"VideoPlayer#playを呼び出す処理"があることが分かります。**

#### VideoPlayerを作るための処理

"VideoPlayerを作るための処理"は、ただ、他の型やクラスを作るためのロジックで、これを"**コンストラクションロジック**"と呼びます。

```kotlin
val videoDatabase = Room
    .databaseBuilder(
        this,
        VideoDatabase::class.java,
        "database"
     )
     .createFromAsset("videos.db")
     .build()
val codecs = listOf(FMP4, WebM, MPEG_TS, AV1)
val videoPlayer = VideoPlayer(videoDatabase, codecs)
```

#### VideoPlayer#playを呼び出す処理

"VideoPlayer#playを呼び出す処理"は、アプリの価値を作るためのロジックとなり、"**ビジネスロジック**"と呼びます。ここではコンストラクションロジック以外をこう呼ばせてください。

```kotlin
videoPlayer.play()
```

コンストラクションロジックとビジネスロジックが一緒になっていると、コードを追ったり、読んだりするのを難しくします。
またそれはクラスを読む人にとってあまり意味のないものであることが多いです。
**Dependency Injection(DI)を行うライブラリでは、このコンストラクションロジックとビジネスロジックを分離することができます。**

### DIについてまとめ

* さまざまな良い影響があるのでDIを行おう！
* インスタンス化のためのボイラープレートを避けるためにDIライブラリを使おう！

# AndroidではDIが難しい

フレームワークがActivityなどのインスタンスを作ってしまいます。**例えば、startActivityなどするとインスタンスが作られてしまいます**。(コンストラクタをいじれません。)

```kotlin
// Androidのフレームワークによって勝手にActivityがインスタンス化される
startActivity(Intent(context, MainActivity::class))
```

AndroidのAPI Level 28からFactoryでActivityを作れるようになるなど、改善はされていますが、Android 9以上でないと動作しないので、現状では現実的ではないです。

# Daggerがこれまでの解決策でした。

**上位1万のアプリで74％がDagger使っており**、今の解決策としてはこれがメインです。
しかし、アンケートによると**49%のユーザーがより良いDIの解決策を必要としていた**ようです。

どんなDIの解決策が必要だったか？

* Opinionated(意見を持った。)  
決めるのを楽にして、使うのを楽にする (後述)
* セットアップが簡単  
(DaggerはAndroidではなく、Java用だったため、Daggerは設定が難しかった)
* 重要な部分にフォーカスできる

これがDagger Hiltが生まれたメインの理由のようです。

# Dagger Hilt
Dagger HiltはDaggerの上に構築されたライブラリです。
Daggerの良いところを使うことができます。
GoogleのAndroidXチームとDaggerチームで共同で作られています。

* AndroidにおけるDIの標準化  
どのようにAndroidでDIするのかを標準化(standardize)します。
* Dagger上に構築  
Daggerのためのコードを生成します。
* アノテーションベース  
Hiltに対して、アノテーションで何をしたいのかを伝えます。
* ツールサポート  
Android Studio 4.1以降では左側にガターアイコンが表示される。例えば依存関係がどこから来たのかがわかります。  
* AndroidX Extension  
ViewModelとWorkManagerで使える。他のライブラリも追加予定です。

## Dagger HiltをVideoPlayerの例で使ってみよう

どうにかして、Dagger HiltにVideoPlayerの作り方を教えないといけないですが、どのように教えたら良いでしょうか？
一番基本的な**Dagger Hiltへのインスタンスの作り方の教え方は、コンストラクタに`@Inject`をつけること**でです。
現状はVideoPlayerに依存関係をもたせていません。
**Dagger Hiltはこのクラスをただインスタンス化するだけなので、作ることができます。**

```kotlin
class VideoPlayer @Inject constructor() {
    private var isPlaying = false

    fun play() {
...
    }
}
```

次にこのアプリはHiltで動くということをHiltに教えないといけません。
AndroidのApplicationクラスで`@HiltAndroidApp`を使うと、このアプリがHiltで動くということを教えられます。また、**`@HiltAndroidApp`を使うと内部的には`Component`が作られます**。この**`Component`とは、コンストラクションロジックと、作ったインスタンスを保持する部分**です。(Componentについては後でもう一度触れます。)

```kotlin
@HiltAndroidApp
class VideoApp : Application()
```

Activtyのコンストラクタがいじれないということでしたが、その対応としてActivityに`@AndroidEntryPoint`をつけます。

```kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
```

`@AndroidEntryPoint`をつけることで、3つのことをHiltに対して教えます。

* "このActivityはInjectionを行う。このActivityはDependency Injectionを使う"
* "ActivityのComponentをこのActivityに追加。"
* "HiltからDependencyを持ってくる"

変数に`@Inject`アノテーションを付けています。
これはHiltからInjectされることを意味します。**"Actiivtyが作られたときに、VideoPlayerをInjectして" とHiltに教えている**形です。
そして、onCreateメソッドではVideoPlayerを呼ぶなど好きなことができます。

```kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    // ↓ Dagger HiltによってonCreateでInjectされる
    @Inject lateinit var videoPlayer: VideoPlayer
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        videoPlayer.play()
    }
}
```

差分: https://github.com/takahirom/hilt-sample-app/commit/6a8a3711808e806e5953712adeb19b11cb73c3a9#diff-bbc9d28d8bcbd080a704cacc92b8cf37R24

### 魔法？どこでフィールドに代入されるの？
少しだけ中身の仕組みを知っておいたほうが分かりやすいと思うので、説明しておきます。
`@AndroidEntryPoint`がついているActivityはHiltによって変換されます。
Hiltによって変換後にMainActivityとAppCompatActivityの間に
**生成されたHilt_MainActivity**が入ります。
**Hilt_MainActivityのonCreateの中でフィールドにInjectされます。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/99101bd3-38a3-55c6-3788-d1221ca740ce.png)

Hiltによって変換後のコード

```kotlin
@AndroidEntryPoint
class MainActivity : Hilt_MainActivity() { // Hilt_MainActivityになっている
    @Inject lateinit var videoPlayer: VideoPlayer
    override fun onCreate(savedInstanceState: Bundle?) {
        // この中でフィールドにInjectされる
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        videoPlayer.play()
    }
}


```


### VideoPlayerにVideoDatabaseをInjectさせるには？

これで一応MainActivityでVideoPlayerが使えるようになりました。ただ、これではVideoPlayerがDatabaseなどにアクセスできません。どのようにしていけばいいでしょうか？

```kotlin
// ↓ Databaseなどを持っていない
class VideoPlayer @Inject constructor() {
    private var isPlaying = false

    fun play() {
...
    }
}
```

もし、**VideoDatabaseのコンストラクタを変更することができるのであれば、同様にVideoDatabaseに `@Inject`をつけて、Hiltにインスタンスの作り方を教えられます。Daggerが勝手に依存関係のインスタンス、VideoDatabaseを先に作ってからVideoPlayerをインスタンス化してくれます。**

```kotlin
class VideoPlayer @Inject constructor(
  private val database: VideoDatabase
) {
    private var isPlaying = false

    fun play() {
...
    }
}
class VideoDatabase @Inject constructor() {
...
}
```

ただ今回はRoomによって作られるインスタンスになるので、コンストラクタをいじって`@Inject`をつけることができません。そのためどうにかしてDagger Hiltに以下のVideoDatabaseの作り方を教えないといけません。

```kotlin
val videoDatabase = Room
    .databaseBuilder(
        this,
        VideoDatabase::class.java,
        "database"
     )
     .createFromAsset("videos.db")
     .build()
```

そこでModuleを使います。**Moduleを使うことで、Hiltにインスタンスの作り方を教えることができます。**
`@Module`と `@InstallIn`というアノテーションを付けるただのクラスです。
そのModuleにはメソッドを追加します。
**Moduleにあるメソッドは料理のレシピだと考えると分かりやすいです。VideoDatabaseの型を作るレシピになり、このレシピをHiltに教えています。**
**このレシピを`@InstallIn`で `SingletonComponent`に置きます。`SingletonComponent` は ApplicationのComponentに追加するということです。**

メソッドをよく見ると`@Provides`と書いてあることが分かります。HiltにどのようにVideoDatabaseを作るのかを教えるメソッドということを教えています。
HiltがVideoDatabaseを作る必要があるときに、このメソッドを実行してインスタンスを返します。
ちなみに、contextクラスのインスタンスが`@ApplicationContext`で提供されていますが、Hiltで事前定義されたクラスもいくつかあり、Hiltがいくつかのインスタンスを提供してくれます。
差分: https://github.com/takahirom/hilt-sample-app/commit/c85a6f668a0bf447c0a4b119f4f6d8cc8c2cff80

```kotlin
@Module
@InstallIn(SingletonComponent::class)
object DataModule {
    @Provides
    fun provideVideoDB(@ApplicationContext context: Context): VideoDatabase {
        return Room
            .databaseBuilder(
                context,
                VideoDatabase::class.java,
                "database"
            )
            .createFromAsset("videos.db")
            .build()
    }
}
```

さて、この `SingletonComponent`が出てきましたが、 Componentについてもう少し触れておきましょう。

# Component

Componentは以下のことができます。

* どのようにオブジェクトをビルドするのかのロジックを持っています。  
VideoDatabaseはこうやって作る。。など  
VideDatabase : Room.databaseBuilder() … .build()
* インスタンス生成の順序のロジックを持っています。   
"VideoDatabaseの後にVideoPlayerを生成する。"など  

* スコープによってインスタンスを使い回します。 (後述)



<!--
逆に混乱させそうなので、コメントアウトしておく
オブジェクトをビルドするのかのロジック、インスタンス生成の順序のロジックについて、内部ではどのようにこれを行っているのでしょうか？

Dagger Hiltによって生成されたコードは以下のようになっていて、Factoryによって作られます。

VideoPlayerに何も依存がない場合(少し簡略化してます)

```java
public final class VideoPlayer_Factory implements Factory<VideoPlayer> {
...
  public VideoPlayer get() {
    // ただVideoPlayerをインスタンス化する！
    return new VideoPlayer();
  }
...
}
```

VideoPlayerがVideoDatabaseに依存している場合(少し簡略化してます)

videoDatabaseProviderからDatabaseを受け取りVideoPlayerをインスタンス化します。
videoDatabaseProviderはこのVideoPlayer_Factoryと同じようなFactoryになっています。

```java
public final class VideoPlayer_Factory implements Factory<VideoPlayer> {
  private final Provider<VideoDatabase> videoDatabaseProvider;

  public VideoPlayer_Factory(Provider<VideoDatabase> videoDatabaseProvider) {
    this.videoDatabaseProvider = videoDatabaseProvider;
  }

  @Override
  public VideoPlayer get() {
    // videoDatabaseProviderからDatabaseを受け取って、VideoPlayerをインスタンス化する！
    return new VideoPlayer(videoDatabaseProvider.get());
  }
...
}
```

このようなFactoryを自分で作ると大変ですが、Dagger Hiltがコードを生成してくれるので、ボイラープレートを減らしてくれます。

-->

# Dagger Hilt標準のComponent

HiltはOpnionedであると言いましたが、Dagger Hiltには**標準のComponentが存在し、Componentの構造について迷わなくて良くなっています。**
この図はComponentの階層を表しており、アプリケーション全体のSingletonComponent、画面回転でも生き残るActivityRetainedCompoent、Activityと紐づくActivityComponentなどのComponentがある構造になっています。
上についているアノテーションはスコープアノテーションです。これについては後ほど説明します
例えば、SingletonComponentやConfigration Changeでも生き残るActivityRetainedComponent、ActivityComponent、そしてFragmentComponentなどが付随します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/05b8545c-ba3b-26eb-6f78-f799c37b6f42.png)
https://dagger.dev/hilt/components より


今回の例ではSingletonComponentにVideoPlayerの作り方とVideoDatabaseの作り方が入っており、その作成順序も入っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/7dd6e87a-0159-45c0-6137-d4e8235c6fe4.png)


# インスタンスを共有したいときはどうするのか
例えば現状ではVideoDatabaseは現在使われるたびにインスタンス化されてしまいます。

```kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var videoPlayer: VideoPlayer
    @Inject
    lateinit var videoPlayer2: VideoPlayer
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        println(videoPlayer.database)
        // VideoDatabase_Impl@764b474 ← ハッシュコードが違う
        println(videoPlayer2.database)
        // VideoDatabase_Impl@a945d9d ← ハッシュコードが違う
    }
}

class VideoPlayer @Inject constructor(
    val database: VideoDatabase
)
@Module
@InstallIn(SingletonComponent::class)
object DataModule {
    @Provides
    fun provideVideoDB(@ApplicationContext context: Context): VideoDatabase {
        return ...
    }
}

```


コネクションを使いまわしたいなど様々な理由で、インスタンスを使いまわしたいことがあります。また今回の例とは関係ないですが、Androidの通信で一般的に使われるOkHttpはインスタンスをアプリ全体で使いまわすとパフォーマンスが良くなります。 (https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/ より)

Activity内で共有したい場合は、**`@ActivityScoped`を使うことでActivityComponentで保持される**ため、
Activity内で同じインスタンスを使い回せます。こ`の@ActivityScoped`をScope Annotationといいます 

これでActivity内でインスタンスを共有できます。
差分: https://github.com/takahirom/hilt-sample-app/commit/f895dfac123a0317b9e0e247af3a48b57388ad5d

```kotlin
@Module
@InstallIn(ActivityComponent::class)
object DataModule {
    @ActivityScoped
    @Provides
    fun provideVideoDB(@ApplicationContext context: Context): VideoDatabase {
...
    }
}
```

`@ActivityScoped`ではActivityが違えば別のインスタンスになってしまいます。アプリ全体で使いまわしたいときは**`@Singleton`のScope Annotationを使うことでSingletonComponentで保持される**ため、アプリ全体で使えます。
差分: https://github.com/takahirom/hilt-sample-app/commit/64cc1b50388cf9c79fba26a774ae86efb1f093bc

```kotlin
@Module
@InstallIn(SingletonComponent::class)
object DataModule {
    @Singleton
    @Provides
    fun provideVideoDB(@ApplicationContext context: Context): VideoDatabase {
...
    }
}
```

VideoDtabaseはSingletonComponent内でインスタンスも保持されるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/7b3e06a0-35ae-a4af-20ee-ca9889cb013a.png)


# Hiltが管理していないクラスから、Hiltの依存関係を使いたい時
Hiltが管理しているMainActivityやVideoPlayerクラスではHiltから依存関係を取得できますが、Hiltが管理していないクラスでは取得が難しい場合があります。
例えば、ContentProviderクラスや、他のライブラリが生成するクラス、Dagger Hiltにマイグレーションしているときの既存のクラスなどです。
ここで、EntryPointという仕組みが使えます。**EntryPointを使うことで、HiltのもつComponentが持つ依存関係にアクセスすることができます。**

これはHiltで管理できていないActivityでHiltがコンストラクションロジックをもつVideoPlayerを使う例となります。
差分: https://github.com/takahirom/hilt-sample-app/commit/d66fb46b395b0c9b6a98ff91bd55f3c4f12c99c9

```kotlin
class NonHiltActivity : AppCompatActivity() {
    @EntryPoint // @EntryPointをつける。
    @InstallIn(SingletonComponent::class)
    interface NonHiltActivityEntryPoint {
        fun videoPlayer(): VideoPlayer
    }

    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
        val entryPoint = EntryPointAccessors.fromApplication(
            applicationContext,
            NonHiltActivityEntryPoint::class.java
        )
        val videoPlayer = entryPoint.videoPlayer()
        videoPlayer.play()
    }
}
```

# テスト

一般的なテストの書き方と同じように自分で対象のオブジェクトnewしてテストを書くことができます。
この場合、自分で依存関係を先に作らなくてはいけません。
差分: https://github.com/takahirom/hilt-sample-app/commit/068082bf7bcb20ecbb1258ac6a3027988d624303

```kotlin
    @Test
    fun normalTest() {
        // メモリ内にDBを作る
        val database = Room.inMemoryDatabaseBuilder(
            ApplicationProvider.getApplicationContext(),
            VideoDatabase::class.java
        ).build()
        val videoPlayer = VideoPlayer(database)

        videoPlayer.play()

        assertThat(videoPlayer.isPlaying, `is`(true))
    }
```

Hiltを使うと、このように**自分で依存関係を作らずにHiltにインスタンスを作らせることができます。**
しかし、今回は実際のDatabaseを使わずにメモリ内にDatabaseを使ってそれを使いたいです。どのようにするでしょうか？

```kotlin
@HiltAndroidTest
class VideoPlayerTest {
    @get:Rule
    var hiltAndroidRule = HiltAndroidRule(this)

    @Inject
    lateinit var videoPlayer: VideoPlayer

    @Test
    fun play() {
        hiltAndroidRule.inject()

        videoPlayer.play()

        assertThat(videoPlayer.isPlaying, `is`(true))
    }
```

ここで**`@UninstallModules(DataModule::class)`することで、Dagger HiltからDataModuleが持つVideoDatabaseの作り方を忘れさせることができます**。
そして、**テストの中で、新たなModuleを定義することで、Databaseを提供することができます。** テストの外でModuleを宣言するとテスト全体でInstallされます。
差分: https://github.com/takahirom/hilt-sample-app/commit/4c862ee62e8dfc133ea6e7e3ff0735c0497cfb6a

```kotlin
@HiltAndroidTest
@UninstallModules(DataModule::class)
@RunWith(RobolectricTestRunner::class)
class VideoPlayerTest {
    @InstallIn(SingletonComponent::class)
    @Module
    class TestDataModule {
        @Provides
        fun provideVideoDatabase(): VideoDatabase {
            return Room.inMemoryDatabaseBuilder(
                ApplicationProvider.getApplicationContext(),
                VideoDatabase::class.java
            ).build()
        }
    }
```

# Daggerからの Dagger Hiltマイグレーションについて
Daggerを知っている人向けの話になりますので、Dagger分からんという方は、へーっていうぐらいで見てください。

DaggerからDagger Hiltに一歩ずつマイグレーションしていく方法について触れておきます。

## Hiltへの導入準備

#### Daggerのライブラリをバージョンアップしておく
→ 普通にマイグレーションするだけです。

#### Daggerのコンポーネントの状況を見てみる
Daggerのコンポーネントの図を出すツールは昔からいろんなプラグインがあります。
Dagger SPI(Service provider interface)というDagger内部の
情報を取り出せるDaggerのAPIのようなものがあるので、それを使っているツールで確認することをおすすめします。

https://github.com/arunkumar9t2/scabbard
https://github.com/Snapchat/dagger-browser
など

そしてある程度、どのコンポーネントが、どのコンポーネントに対応付けられそうかを見てみます。
scabbardを使った図の例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/d5bc52c8-82d2-aec9-71f2-c202b7ee9b17.png)

## 今回の状況の前提
例とする状況はApplicationレベルのコンポーネントのAppComponentがあり、その下にActivityごとのComponentがたくさんあるような形になっているとします。
(Daggerだとコンポーネントの形が標準化されていないので、そもそものComponentの形もさまざまになります。)


#### AppComponentの作り方でModuleを引数を渡す形で作っている場合はそれをやめる
Dagger Hiltはこの**モジュールを渡し方して作る作り方をサポートしていないためやめる必要があります。**Moduleの引数をなくして参照してあげることで可能です。
Daggerのドキュメント的にもそれは良くないらしいです。(don't do thisって書いてあります https://dagger.dev/dev-guide/testing )

NG

```kotlin
    DaggerAppComponent
      .builder()
      .networkModule(networkModule)
　　　 .build()
```

OK

```kotlin
    DaggerAppComponent.factory()
      .create(application)
```


## Hiltを導入してAppComponentを置き換える

### ApplicationレベルのComponentをDagger HiltのSingletonComponentに置き換える

Dagger Hiltのライブラリを導入します。これは基本的なドキュメントをご確認ください。
https://dagger.dev/hilt/migration-guide

少しずつマイグレーションしていく場合は`disableModulesHaveInstallInCheck`を入れる必要があります。

Dagger Hiltで標準だと`@InstallIn`が入っていない既存のモジュールがあるときにエラーになってしまいます。**このオプションを入れるとそのエラーを出さなくして、ただライブラリを入れるだけということができます。**

```groovy
 javaCompileOptions {
      annotationProcessorOptions {
           // ↓ **ここが+=になっていないとDagger Hiltのプラグインがargumentを追加するのでハマるので注意**
           arguments += [
               "dagger.hilt.disableModulesHaveInstallInCheck": "true"
           ]
      }
  }
```

Dagger Hiltの**EntryPointはComponentから依存関係をとってくるだけでなく、サブコンポーネントを作ることもできるので、その機能を使って置き換えます**。 (ここは説明飛ばします。)
差分: https://github.com/takahirom/hilt-sample-app/commit/8e542f191bb50ce50db30cb2a72a569f7d17b178

```kotlin
@Subcomponent
interface JustDaggerComponent {
    @Subcomponent.Factory
    interface Factory {
        fun create(): JustDaggerComponent
    }

    fun inject(justDaggerActivity: JustDaggerActivity)
}

@InstallIn(SingletonComponent::class)
@EntryPoint
interface JustDaggerEntryPoint {
    fun activityComponentFactory(): JustDaggerComponent.Factory
}

class JustDaggerActivity : AppCompatActivity() {
    @Inject lateinit var videoPlayer: VideoPlayer
    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
        // old: appComponent.justDaggerComponent().inject(this)
        val entryPoint = EntryPointAccessors.fromApplication(
            applicationContext,
            JustDaggerEntryPoint::class.java
        )
        entryPoint.activityComponentFactory().create().inject(this)

        videoPlayer.play()
    }
}
```

### 既存のActivityのComponentをHiltに置き換えていく

基本的に `@AndroidEntryPoint`をつけて、既存のDaggerの処理を外していくということになります。
`JustDaggerActivity`の形を`MainActivity`の形に変えていきます。

マイグレーションについては、他にもいろいろありますが、コードラボが詳しいので考えている方はCodelabをやってみてください。

# Dagger HiltとJetpackの連携

よく開発で利用されるViewModelやWorkManagerといったJetpackのComponentと連携するライブラリが提供されていて、それを利用することができます。
差分: https://github.com/takahirom/hilt-sample-app/commit/1bec3370fec0fd5b4233db1884e8427bcf91a540
Androidアプリの開発ではViewModelをよく使います。まずはViewModelについて見ていきましょう。
ViewModelではコンストラクタに変更が加えられますが、通常、ProviderやFactoryなどを経由して作成するため難しいですが、この部分を**Dagger Hiltはうまく隠蔽して簡単にViewModelを作ってくれます。**

```kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private val videoPlayerViewModel: VideoPlayerViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        videoPlayerViewModel.play()
    }
}

class VideoPlayerViewModel @ViewModelInject constructor(
    private val videoPlayer: VideoPlayer
) : ViewModel() {
    fun play() {
        videoPlayer.play()
    }
}
```

# Dagger Hilt 実践プラクティス

## Google(or Googler)のサンプルを参照する
迷ったら、サンプルアプリ達を見てみましょう。

Architecture Samples
https://github.com/android/architecture-samples/tree/dev-hilt
Goole I/Oアプリ
https://github.com/google/iosched
Sunflower
https://github.com/android/sunflower
chrisbanes/tivi
https://github.com/chrisbanes/tivi


## fastInitモードが有効になるので、影響を確認する
Dagger HiltはComponentの形が標準化されたことによって、たくさんのSingletonComponentなどのコンポーネントに、この型はこう作るなどのバインディングが入るようになります。
通常、Daggerではこのバインディングの数が増えると増えるだけインスタンス化に時間がかかります。Dagger Hiltを入れたタイミングで通常のモードではなくfastInitモードが有効になることで、これが時間がかからなくなります。しかし、この処理にはトレードオフもあるようなので、**リリース後にFirebase PerformanceやAndroid Vitalsなどで確認してみましょう。**

```kotlin
    val PROCESSOR_OPTIONS = listOf(
      "dagger.fastInit" to "enabled",
```

https://github.com/google/dagger/blob/d3c1d2025a87201497aacb0a294f41b322767a09/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt#L108

<details>
<summary>生成されるコードの比較</summary>
<div>
fastInitがdisabledになっている場合

```kotlin
public final class DaggerApp_HiltComponents_SingletonC extends App_HiltComponents.SingletonC {
  private Provider<Context> provideContextProvider;

  private Provider<VideoDatabase> provideVideoDBProvider;

  private DaggerApp_HiltComponents_SingletonC(
      ApplicationContextModule applicationContextModuleParam) {

    initialize(applicationContextModuleParam);
  }
...

  @SuppressWarnings("unchecked")
  private void initialize(final ApplicationContextModule applicationContextModuleParam) {
    this.provideContextProvider = ApplicationContextModule_ProvideContextFactory.create(applicationContextModuleParam);
    this.provideVideoDBProvider = DoubleCheck.provider(DataModule_ProvideVideoDBFactory.create(provideContextProvider));
  }


  @Override
  public VideoPlayer videoPlayer() {
    return new VideoPlayer(provideVideoDBProvider.get());
  }
```

fastInitがenabled
Providerが値を保持する代わりにComponentが値を保持するようになる。

```kotlin
public final class DaggerApp_HiltComponents_SingletonC extends App_HiltComponents.SingletonC {
  private final ApplicationContextModule applicationContextModule;

  private volatile Object videoDatabase = new MemoizedSentinel();

  private DaggerApp_HiltComponents_SingletonC(
      ApplicationContextModule applicationContextModuleParam) {
    this.applicationContextModule = applicationContextModuleParam;
  }

  private VideoDatabase videoDatabase() {
    Object local = videoDatabase;
    if (local instanceof MemoizedSentinel) {
      synchronized (local) {
        local = videoDatabase;
        if (local instanceof MemoizedSentinel) {
          local = DataModule_ProvideVideoDBFactory.provideVideoDB(ApplicationContextModule_ProvideContextFactory.provideContext(applicationContextModule));
          videoDatabase = DoubleCheck.reentrantCheck(videoDatabase, local);
        }
      }
    }
    return (VideoDatabase) local;
  }

  @Override
  public VideoPlayer videoPlayer() {
    return new VideoPlayer(videoDatabase());
  }
```

</div>
</details>

## 詳細画面などでIDを渡していきたい場合はどうするのか？
Dagger HiltではComponentの構造が標準化されているため、例えばEpisodeDetailComponentを作って、そこで画面詳細IDを配布というようなことは難しいです。
これに対してさまざまなやり方が考えられますが、Googleのサンプルでのやり方は一つのようです。
**Daggerを使って配布せず、直接渡す方法です。**
HiltのComponentを作るための公式のページに、バックグラウンドタスクの話で少し文脈は違うのですが、"だいたいは自分で渡したほうがシンプルで十分"という話が出てきます。
一番渡しがちなAssisted InjectについてはただViewModelに渡すときは少しだけ工夫することができます。

https://dagger.dev/hilt/custom-components

> for most background tasks, a component really isn’t necessary and only adds complexity where simply passing a couple objects on the call stack is simpler and sufficient.

コールスタックで引数を渡すだけのほうがシンプルで十分で、Componentは複雑さを増すだけ。ということを言っています。


Architecture Samples
https://github.com/android/architecture-samples/blob/dev-hilt/app/src/main/java/com/example/android/architecture/blueprints/todoapp/taskdetail/TaskDetailFragment.kt#L90
Iosched
https://github.com/google/iosched/blob/b428d2be4bb96bd423e47cb709c906ce5d02150f/mobile/src/main/java/com/google/samples/apps/iosched/ui/speaker/SpeakerViewModel.kt#L101
Sunflower
https://github.com/android/sunflower/blob/2bbe628f3eb697091567c3be8f756cfb7eb7258a/app/src/main/java/com/google/samples/apps/sunflower/PlantDetailFragment.kt#L55
chrisbanes/tivi
https://github.com/chrisbanes/tivi/blob/27348c6e4705c707ceaa1edc1a3080efa06109ae/ui-showdetails/src/main/java/app/tivi/showdetails/details/ShowDetailsFragment.kt#L60


## ViewModelにコンストラクタで値を渡す
“IDなどを直接渡していく”ということでしたが、ViewModelのコンストラクタで渡せないと、lateinitにしたり、nullableにして少し安全じゃない形になっちゃいますよね？
AssistedInjectというライブラリで、コンストラクタ引数を渡す実装ができる。(今後Daggerにも組み込まれるそうです)

差分: https://github.com/takahirom/hilt-sample-app/commit/6584808f8fe13cc92317df50d413f828d1dfdf00

DaggerはAssistedInjectと呼ばれるものを対応しようとしています。これはInjectするときに、プログラムから引数で値を渡せるというものです。
https://github.com/google/dagger/issues/1825

これを先に使えるライブラリがあります。
https://github.com/square/AssistedInject

具体的にはGooglerの以下のgistの内容を使ってViewModelにコンストラクタで値を渡すことができます。
https://gist.github.com/manuelvicnt/437668cda3a891d347e134b1de29aee1

本質的に理解しようとすると大変なので、仕組みが気になる方は以下を読んでみてください。
https://qiita.com/takahirom/items/f28ceb7a6d4e69e4dafe


## EntryPointの定義場所

EntryPointは、基本的にはGoogleのサンプルでは使われていないようです。
大きいアプリのマイグレーションなどでは使われると思われるので、紹介しておきます。
EntryPointはどこにでも書くことができますが、どこに書くのがいいでしょうか？
依存関係を取得するときに使うEntryPointは、使う場合には必要な依存関係のみを取得したほうが依存するオブジェクトを少なくできるので、基本的には**取得する場所に定義して利用していきましょう**。

```kotlin
class NonHiltActivity : AppCompatActivity() {
    @EntryPoint
    @InstallIn(SingletonComponent::class)
    interface NonHiltActivityEntryPoint {
        fun videoPlayer(): VideoPlayer
    }

    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
        val entryPoint = EntryPointAccessors.fromApplication(
            applicationContext,
            NonHiltActivityEntryPoint::class.java
        )
        val videoPlayer = entryPoint.videoPlayer()
        videoPlayer.play()
    }
}
```

# マルチモジュール
まだちゃんとしたベストプラクティスがあるわけではないと思いますが、Dagger Hiltとマルチモジュールについて考えておきます。以下のようなモジュール構成があったとします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/7438dee7-be10-9032-15ee-4789bc74ef19.png)

> Applicationクラスをコンパイルする Gradleモジュールは、
すべての Hilt モジュールおよびコンストラクタ インジェクションで注入するクラスを
**推移的依存関係に含める必要があります**。

https://developer.android.com/training/dependency-injection/hilt-multi-module?hl=ja より

ということなので、以下のように**ルートのモジュールからDaggerのModuleを持つGradleモジュールへの参照が必要になります**。
この部分に関して、**真ん中の形が色んなパターンで無駄に依存関係を増やさずに動くので、いいのかなとは思う**のですが、まだベストプラクティスと言えるものはないです。
**ただ、モジュールを作ったときにクラスパスに含めるだけで`@InstallIn`されたModuleがコンポーネントにインストールされて使えるので、すごく楽で、本当に使いやすいです。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/43702f3c-f8ab-e645-a5a4-dfab7ccecc8c.png)


参考
Googleの方のchrisbanes/tiviのアプリのappモジュールからの参照
ルートのGradleモジュールから各モジュールに参照していることが分かる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/c750f6d4-edb9-19a0-787b-0f308ab538a5.png)

Hiltでビルドできる？できない？みたいな実験する環境作るのめんどうですよね？Hiltが導入済みのアプリで実験したくなったらこのサンプルプロジェクトを用意しているので、実験してみてください。
https://github.com/takahirom/dagger-transitive-playground/tree/hilt


## テストのプラクティス
### 実際の依存関係を使おう
Hilt Testing Philosophyというページが有り、Dagger Hiltを使ってテストをする際のプラクティスが書いてあります。これがかなり主張を含んだもので面白いのでぜひ読んでみてください。
https://dagger.dev/hilt/testing-philosophy
こちらに自分のメモがあります。
https://qiita.com/takahirom/items/a3e406b067ad645605da

これによると2つ言いたいことがあるようです。

* ユーザーからの観点でテストする  
ユーザーとは実際のユーザーもクラスのユーザーも、APIのユーザーも含む。internalなメソッド名や実装になど依存せず、テストが壊れることがユーザーの観点から変更があったことを意味する
* 実際の依存関係を利用する

#### なぜ実際の依存関係を使うか？
* 実際の依存関係は**本当の問題を捕捉しやすい**。モックのように古いまま残されたりしない。
* "ユーザーからの観点でテスト"と組み合わせることで、**同じカバレッジでもっと少ないテストの量で書くことができる**。
* **テストが壊れることが、FakeやMockの設定ミスによる問題による問題の代わりに、実際の問題を指し示す** (そして、逆に言えばテストがパスすることはコードがちゃんと動くことを意味する)
* "ユーザーからの観点でテスト"と"実際の依存関係を使う"は相性が良い。依存関係を入れ替えないため。

#### どうやってDagger Hiltで実際の依存関係を使うか？
普通にテストして実際の依存関係を使おうとすると依存関係を作るための**ボイラープレートが発生します**。
PlayerFragmentを作るためにViewModelのFactoryが必要で、ViewModelを作るにはVideoPlayerが必要で、VideoPlayerを作るにはVideoDatabaseが必要になるなどを書いていくと。。**大変になります。**

```kotlin
launchFragment {
    // たいへん！
    PlayerFragment().apply {
        videoPlayerViewModelAssistedFactory =
            object : VideoPlayerViewModel.AssistedFactory {
                override fun create(videoId: String): VideoPlayerViewModel {
                    return VideoPlayerViewModel(
                        videoPlayer = VideoPlayer(
                            database = Room.inMemoryDatabaseBuilder(
                                ApplicationProvider.getApplicationContext(),
                                VideoDatabase::class.java
                            ).build()
                        ),
                        videoId = "video_id"
                    )
                }
            }
    }
}
onView(withText("playing")).check(matches(isDisplayed()))
```

紹介したように、Dagger Hiltを使ってテストでも**Injectを行うことで実際の依存関係を使ってテストをすることが可能になります。**

```kotlin
@RunWith(AndroidJUnit4::class)
@HiltAndroidTest
@UninstallModules(DataModule::class)
class AndroidPlayerFragmentTest {
    @InstallIn(SingletonComponent::class)
    @Module
    class TestDataModule {
        @Provides
        fun provideVideoDatabase(): VideoDatabase {
            return Room.inMemoryDatabaseBuilder(
                ApplicationProvider.getApplicationContext(),
                VideoDatabase::class.java
            ).build()
        }
    }

    @get:Rule
    var hiltAndroidRule = HiltAndroidRule(this)

    @Test
    fun play() {
        hiltAndroidRule.inject()
        launchFragmentInHiltContainer<PlayerFragment> {
        }
        onView(withText("playing")).check(matches(isDisplayed()))
    }
```

#### さまざまなデメリットがあるのでTestでのCustom Applicationクラスはやめよう
一応`@CustomTestApplication`でカスタムアプリケーションを使ってテストできます。
テスト用のTestAppクラスなどを作っているのであればHiltと組み合わせると
さまざまなデメリットが発生します。また一般的にやめておいたほうが良さそうです。

* テストでは以下の問題があるため、:*カスタムアプリケーションでは@Injectのフィールドを使えない**。(コンパイルエラー)
* Applicationはテストをまたいで生存してしまうので、テストをまたいだ**状態のリークを起こす**。
* **子が親に依存するテストになってしまう** ので、テストの独立性を高めるためにやめておくべき。など


# Dagger Hiltの細かいTips

* 変換されたコードのAndroid Studioでの実行が現状、サポートされていないため、**Gradleでテストを実行する必要がある。**(Android StudioでGradleの設定を行ってAndroid Studioで実行させることもできる。)  
詳しくは: https://dagger.dev/hilt/gradle-setup.html#running-with-android-studio
**Android Studio 4.1からRun/Debug Configrationsが保存できるようになったので、活用できます。** 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/7cfbb9fa-1599-c3df-5b75-b274498662f7.png)
差分: https://github.com/takahirom/hilt-sample-app/commit/2274ff3b5712e6b266cf022ff91f4581532bf45b


* テストを楽にするルールを作れる

HiltAndroidAutoInjectRuleなどを用意しておくと自分でInjectを呼ばなくても動くようになります。

```kotlin
@get:Rule val hiltAndroidAutoInjectRule = HiltAndroidAutoInjectRule(this)

class HiltAndroidAutoInjectRule(testInstance: Any) : TestRule {
  private val hiltAndroidRule = HiltAndroidRule(testInstance)
  private val delegate = RuleChain
    .outerRule(hiltAndroidRule)
    .around(HiltInjectRule(hiltAndroidRule))

  override fun apply(base: Statement?, description: Description?): Statement {
    return delegate.apply(base, description)
  }
}
class HiltInjectRule(val rule: HiltAndroidRule) : TestWatcher() {
  override fun starting(description: Description?) {
    super.starting(description)
    rule.inject()
  }
}
```

# まとめ
以下について話してきました。 

* なぜDIを使うのか
* なぜDagger Hiltなのか
* 基本的な使い方や概念
* テスト
* マイグレーション
* 実践プラクティス
* Tips

Dagger Hiltを使うことでさまざまないい効果が見込めるので、Dagger Hiltを使ってアプリを作ってみましょう！

# 参考

公式ウェブページ
https://dagger.dev/hilt/
https://developer.android.com/training/dependency-injection/hilt-android?hl=ja

Android Dependency Injection
https://www.youtube.com/watch?v=B56oV3IHMxg
Dagger Hilt Deep Dive
https://www.youtube.com/watch?v=4di2TTqeCrE

Architecture Samples
https://github.com/android/architecture-samples/tree/dev-hilt
Goole I/Oアプリ
https://github.com/google/iosched
Sunflower
https://github.com/android/sunflower
chrisbanes/tivi
https://github.com/chrisbanes/tivi