LoginSignup
9
7

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-09-25

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

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

マイグレーション前

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

コンポーネントの木
image.png
今回はSubComponentという仕組みが使われており、コンポーネント同士が依存することができ木構造になっており、RegistrationComponentではAppComponentの内容が使えるようになっています。

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

@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になっている)

@ActivityScope
@Subcomponent
interface LoginComponent {
...
}
@ActivityScope
class RegistrationViewModel @Inject constructor(val userManager: UserManager) {


詳細なグラフ

AppComponent
image.png

RegistrationComponent
image.png

LoginComponent
image.png

UserComponent
com.example.android.dagger.user.UserComponent.png


中間までマイグレーション

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

コンポーネントの木
image.png

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

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

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

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

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

@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が可能になります。

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)
...
    }


詳細なグラフ

AppComponent
image.png

ActivityComponent
image.png

UserComponent
image.png


マイグレーション後

完全にUserComponentが削除されており、HiltのComponentが完全に使われています。
image.png

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


詳細なグラフ

image.png
image.png

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7