【Compose Multiplatform】依存性注入を対応させる(Hilt -> Koin)
はじめに
ナビゲーションに続き、依存性注入(DI)をCompose Multiplatformに対応させました。
依存性注入は、AndroidプロジェクトではHilt
を使うことがデファクトスタンダードになっていますが、こちらはCompose Multiplatformに対応しておりません。
Compose Multiplatform Wizardによると、Koin
というライブラリが推奨されていたので、こちらに移行していきたいと思います。
こちらの記事では、それぞれの詳細な説明というよりも、Hilt
とKoin
との対応関係を明確化させることを中心に書いていきたいと思います。(依存性注入の知識が、自分の中でそこまでかっちりとしていないのもあります...)
Koinの公式サイト
HiltとKoinの違い
Hilt | Koin | |
---|---|---|
使用開始 | Application継承クラスで@HiltAndroidApp | Application継承クラスでstartKoin{modules(appModule)} |
モジュール寿命 | Singletonm, ViewModel, Activity, Fragment, View...など8種類(公式) | single, facrory, scopeの3種類(公式) |
モジュールの定義 | @Provides fun provideHoge という関数をもつ、@ModuleをつけたHogeModuleを定義 | val hogeModule = module{}というグローバル変数として定義 |
注入の仕方 | クラスのコンストラクタかクラス内に、@Injectをつけた変数(または関数)として定義 | KoinComponent継承クラスで val hoge: Hoge by inject() で定義 |
アノテーション | たくさん使う | 使わない |
理解の難易度 | 暗黙の約束が多く、最初はなんで動くのか意味不明 | 明快 |
Koinの導入
下記のように依存関係を追加します。執筆時の最新バージョンは3.4.3
でした。
implementation "io.insert-koin:koin-android:$koin_android_version"
Koinの使用開始
Hiltをすでに使っている場合は、Application
クラスを継承したクラスを@HiltAndroidApp
アノテーションをつけて定義しているので、それをそのまま使います。
@HiltAndroidApp
class MainApplication: Application() {
override fun onCreate() {
super.onCreate()
// 各種処理
}
}
そのクラスからHilt用のアノテーションを削除して、開始処理を追加します。
- @HiltAndroidApp
class MainApplication: Application() {
override fun onCreate() {
super.onCreate()
+ startKoin{
+ modules(appModule) // appModuleは、のちに定義するモジュール群
+ }
// 各種処理
}
}
注入のための三人衆の定義
interface
, impl
(implementation), module
の三人衆を定義します。(勝手にそう呼んでます)
Hilt
での三人衆
① interface
の定義
interface hogeRepository {
fun getHoge(): Hoge
}
② impl
の定義
class HogeRepositoryImpl: HogeRepository {
override fun getHoge(): Hoge {
return // 具体的な処理
}
③ module
の定義
@Module
@InstallIn(SingletonComponent::class)
class HogeRepositoryModule() {
@Provides
@Singleton
fun provideHoge(hogeImpl: HogeImpl): Hoge {
return hogeImpl
}
}
Koin
での三人衆
① interface
の定義(Hilt
と同じ)
interface hogeRepository {
fun getHoge(): Hoge
}
② impl
の定義(ここで何も注入したりしない場合、Hilt
と同じ)
class HogeRepositoryImpl: HogeRepository {
override fun getHoge(): Hoge {
return // 具体的な処理
}
③ module
の定義
val hogeRepositoryModule = module {
single<HogeRepository> { HogeRepositoryImpl() }
}
注入してみる
Hilt
で注入する(3種類)
Hilt
での注入は以下のように行いました。(一般的な場合: コンストラクタインジェクション)
class UsingHogeViewModel @Inject(hogeRepository: HogeRepository): ViewModel(またはScreenModel) {
val hoge = hogeRepository.getHoge()
// 各種処理
}
またはこういう書き方もありなようです。(ActivityやFragmentなど、コンストラクタをいじれない場合: フィールドインジェクション (参考))
class UsingHogeViewModel: ViewModel(またはScreenModel) {
@Inject
lateinit var hogeRepository: HogeRepository
val hoge: Hoge
get() = hogeRepository.getHoge()
// 各種処理
}
ここで、val hoge = hogeRepository.getHoge()
として使うことはできず、getメソッドを使う必要がある点に注意です。(参考)
もしくは、こういう書き方もアリなようです。(あるメソッド内でしか使わない場合など: メソッドインジェクション)
class UsingHogeViewModel: ViewModel(またはScreenModel) {
@Inject
fun getHoge(hogeRepository: HogeRepository): Hoge {
return hogeRepository.getHoge()
}
// 各種処理
}
Koin
で注入する
Koin
では以下のようになります。
- class UsingHogeViewModel @Inject(hogeRepository: HogeRepository): ViewModel(またはScreenModel) {
+ class UsingHogeViewModel: ViewModel(またはScreenModel), KoinComponent {
+ val hogeRepository: HogeRepository by inject()
val hoge = hogeRepository.getHoge()
// 各種処理
}
おわりに
Koin
の方が暗黙の了解が少ないため、個人的には非常に好みです。実装もシンプルになってマルチプラットフォーム対応もできるとは一石二鳥ではないですか。もちろんHilt
の方が、モジュールの寿命をもっと高解像度に定義したい場合などは優れていると思いますし、Googleに公式でサポートされているのは強いですね。
まだまだ依存性注入について深く語れるほど知識がないので、やや雑な記事になってしまいましたが、勉強を重ねていきたいと思います。
Discussion