背景
今まで Kotlin + Dagger2 で AppComponent にアクセスするコードがあった。
@Component(modules=[ ... ])
interface AppComponent {
val myModule: MyModule
// ...
}
dagger2 拡張関数
val Activity.appComponent: AppComponent
get() = (applicationContext as MainApplication).appComponent
fun <T : Any> Activity.dagger2(block: (AppComponent) -> T) = lazy {
block(appComponent)
}
この dagger2 拡張関数があれば Dagger-Android を使わなくても手軽にインスタンスにアクセスできた。
class MainActivity : Activity() {
private val myModule by dagger2 { it.myModule }
// ...
}
困ったこと
しかし Dagger-Hilt では、 AppComponent は自動生成であり自分で定義しないため、この書き方はできなくなってしまった。
(更新) ちゃんとしたEntryPointの使い方はこちら
公式ドキュメントが公開されました。
(古い) 解決方法
@EntryPoint
を使う
従来 @Component
に書いていた記述は @EntryPoint
を使うと実現できる。
@EntryPoint
@InstallIn(ApplicationComponent::class)
interface AppComponentEntryPoint {
val myModule: MyModule
}
dagger2 拡張関数を書き換える
拡張関数の実装を少しだけ書き換えれば今までのやり方を活かすことができる。
val Activity.appComponent: AppComponentEntryPoint
get() {
// ドキュメントを見て修正
EntryPoints.get(applicationContext, AppComponentEntryPoint::class.java)
// @Suppress("UNCHECKED_CAST")
// return (applicationContext as Hilt_MainApplication)
// .generatedComponent() as AppComponentEntryPoint
}
fun <T : Any> Activity.dagger2(block: (AppComponentEntryPoint) -> T) = lazy {
block(appComponent)
}
利用する側は書き換えの必要なし
class MainActivity : Activity() {
private val myModule by dagger2 { it.myModule }
// ...
}
なぜこれがうまくいくのか?
MainApplication はソースコード上は Application を継承しているように見える。
@HiltAndroidApp
class MainApplication : Application() {
// ...
}
しかし、 Gradle plugin のパワーにより Hilt_MainApplication という自動生成されたクラスを継承するようにビルド時に書き換わる。
Hilt_MainApplication
のメソッドである generatedComponent()
から自動生成されたComponentが取得できる。
自動生成されたComponentは @EntryPoint
がついていた AppComponentEntryPoint を継承している。
したがってうまくインスタンスを得ることができる。
まとめると
AppComponent にアクセスしていたコードも少しの書き換えで Dagger-Hilt させることができ、少しずつ Dagger-Hilt らしい書き方に寄せていくことが可能そうである。
(Dagger-Hilt はまだalpha版であるし、この書き方がいつの間にかできなくなる可能性はある。)
最後に
Dagger-Hilt のインストール方法等はこちらの記事を参考にさせて頂きました。
わかりやすい記事をありがとうございます。