生成された図で見るDagger Hiltへのマイグレーション

Dagger Hiltへのマイグレーションのコードラボは数ヶ月前にやっていて忘れてきたので、どういう内容だったのか後から振り替えれるようにグラフをおいておきます。
ちょっと先にDagger Hiltのチュートリアル的なのは他の記事などでご確認ください。

https://codelabs.developers.google.com/codelabs/android-dagger-to-hilt/#1

またDagger SPIを使ったいい感じのGradle Pluginも見つけたのでそれで見ていきます。
https://github.com/arunkumar9t2/scabbard

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">今Daggerをグラフ化して見るのはDaggerの公式SPI(Service provider interface)を使っているこれが良さそうかも👀 <a href="https://t.co/IzVaZJofuo">https://t.co/IzVaZJofuo</a></p>&mdash; takahirom (@new_runnable) <a href="https://twitter.com/new_runnable/status/1309355880857481216?ref_src=twsrc%5Etfw">September 25, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## マイグレーション前
Daggerでは、DaggerのComponent、DIコンテナでオブジェクトをインスタンス化するためのロジック(コンストラクションロジック)とインスタンスが保持されています。
AppComponentにはアプリケーション全体で保持するコンストラクションロジックとインスタンスを保持しており、RegistrationComponentには登録のときに使うコンストラクションロジックとインスタンスが入っています。

コンポーネントの木
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/fc5e5b8a-9ef2-2361-6fd2-081c41e8e099.png)
今回はSubComponentという仕組みが使われており、コンポーネント同士が依存することができ木構造になっており、RegistrationComponentではAppComponentの内容が使えるようになっています。

コードでのSubComponentの作り方(詳細は説明しません)

```kotlin
@Singleton
@Component(modules = [StorageModule::class, AppSubcomponents::class])
interface AppComponent {
...
     // サブコンポーネントのFactoryを取得できるようにする
    fun registrationComponent(): RegistrationComponent.Factory
    fun loginComponent(): LoginComponent.Factory
...
}
@Module(
    subcomponents = [ // サブコンポーネントを指定する
        RegistrationComponent::class,
        LoginComponent::class,
        UserComponent::class
    ]
)
class AppSubcomponents

@ActivityScope
@Subcomponent
interface RegistrationComponent {
    @Subcomponent.Factory
    interface Factory {
        fun create(): RegistrationComponent
    }
...
}
```

そしてRegistrationComponentとLoginComponentでは `@ActivityScope`と書いており、これを使うことでこのComponentのDIコンテナを指定してインスタンスを配布したり、同じインスタンスを共有したりできるようにしているようです。 (このサンプルでは、ViewModelがこの`@ActivityScope`になっている)

```kotlin
@ActivityScope
@Subcomponent
interface LoginComponent {
...
}
```

```kotlin
@ActivityScope
class RegistrationViewModel @Inject constructor(val userManager: UserManager) {
```

<details>
<summary>詳細なグラフ</summary>
<div>
AppComponent
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/73e3704c-9833-908e-b649-5442d7db198f.png)


RegistrationComponent
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/2ce01187-a517-a26e-9044-76e147188230.png)

LoginComponent
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/2a0af8b4-ddee-5b94-cd49-5ea32abced18.png)

UserComponent
![com.example.android.dagger.user.UserComponent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/35927ee3-ac11-7054-bd14-8c8c32d5a8a6.png)
</div>
</details>
## 中間までマイグレーション

Dagger HiltではDaggerのアプリのコンポーネントの構造を標準化するので、このようにActivityComponent(内部的にはActivityRetainedC)などのComponentが追加されています。
中間までのマイグレーションなので、UserComponentが残っています。

コンポーネントの木
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/f0463969-9e66-9643-e266-ebf588512f45.png)

Dagger Hiltのコード生成によって、ActivityComponent(内部的にはActivityRetainedC)など勝手にSubCompopnentが作られます。
しかし、UserComponentは自分で作ったComopnentなので、生成させられません。
どのようにしたかというと、`InstallIn`でApplicationComponentにModuleを追加し、そこでsubcomponentsを指定しています。

```kotlin
@InstallIn(ApplicationComponent::class)
@Module(
    subcomponents = [
        UserComponent::class
    ]
)
class AppSubcomponents
```

そしてUserComponent.FactoryをUserManagerにInjectさせることで、UserComponentを作成し利用できます。

```kotlin
@Singleton
class UserManager @Inject constructor(
    private val storage: Storage,
    private val userComponentFactory: UserComponent.Factory
) {
```

ただ、UserComponentはDagger Hiltの外のComponentなので、このUserComponentの管理は自分で行う必要があります。

```kotlin
@Singleton
class UserManager @Inject constructor(
    private val storage: Storage,
    private val userComponentFactory: UserComponent.Factory
) {


    var userComponent: UserComponent? = null
        private set
...

    fun registerUser(username: String, password: String) {
...
        userJustLoggedIn()
    }

    fun loginUser(username: String, password: String): Boolean {
...
        userJustLoggedIn()
        return true
    }

    fun logout() {
        // When the user logs out, we remove the instance of UserComponent from memory
        userComponent = null
    }

    private fun userJustLoggedIn() {
        userComponent = userComponentFactory.create()
    }
}
```

ここでUserComponentによるInjectを使う上で一つ問題があります。
MainActivityでUserComponentによるInjectが使うためには、UserManagerのインスタンスが必要です。UserComponentのDIコンテナでInjectしたいのでMainActivityには`@AndroidEntryPoint`をつけてHiltのInjectを使うことができません。
こういうときにDagger HiltのDIコンテナが持つインスタンスを受け取る方法があります。 `EntryPointAccessor`を使うというもので、これによってUserManagerを取得して、UserManagerのもつuserComponentでinjectが可能になります。

```kotlin
class MainActivity : AppCompatActivity() {
    @InstallIn(ApplicationComponent::class)
    @EntryPoint
    interface UserManagerEntryPoint {
        fun userManager(): UserManager
    }
...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Grabs instance of UserManager from the application graph
        val entryPoint = EntryPointAccessors.fromApplication(applicationContext, UserManagerEntryPoint::class.java)
        val userManager = entryPoint.userManager()
...
            userManager.userComponent!!.inject(this)
...
    }
```


<details>
<summary>詳細なグラフ</summary>
<div>
AppComponent
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/ee23b2c6-7e74-4fd2-f9da-0d1bd60ceb31.png)

ActivityComponent
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/c9c8f4dd-4f81-829d-67e4-8c6f693c1dc9.png)

UserComponent
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/eacac291-f3e1-139b-59c6-2370a1ebdd27.png)
</div>
</details>

## マイグレーション後

完全にUserComponentが削除されており、HiltのComponentが完全に使われています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/49f24ecd-fdf5-6aff-6837-0eb532c2478b.png)

ちなみに`@Singleton`なUserRepositoryを使って、その中でログイン中かなどを管理することで、解決しているようです。

<details>
<summary>詳細なグラフ</summary>
<div>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/6983d58d-1b0c-ef92-db5e-e15d51af0170.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/1c10d3ea-d7f4-6def-4945-8a56330b2e7a.png)
</div>
</details>