はじめに
Androidプロジェクトで使っているKotlinのバージョンが1.3系列だったので1.4系列に上げたところエラー。
どうやら使っているKoinのバージョン(2.1.6)だとKotlin 1.4系列に対応していないようです。
新しいバージョンなら対応しているそうなのでじゃあKoinもいっちょ上げてやろうと上げてみたらソースコードがエラーで火を吹いた🔥
Koinの2.1.6から2.2.1にかけてbreaking change(下位互換性のない変更)があったようです……。セマンティックバージョニングに従ってよ……。
どこをどう変えればビルドが通るようにマイグレーションできるのか探したところ What’s next with Koin? — 2.2 & 3.0 releases という公式の記事を見つけましたが、マイグレーションの観点からは書かれていないので読み解くのにちょっと大変でした。
ということで本記事はマイグレーションの観点からの忘備録になります。新機能の紹介はしていないのでご注意ください。
環境
- Koin 2.1.6 -> 2.2.1
- Kotlin 1.4.20
- Androidプロジェクトで使用
- Android Xを使用
2.1.6以外の2.1系列だとまた事情が違うかもしれません。ご注意ください。
マイグレーション
以降ソースコード中の -
は行の削除を、 +
は行の追加を表します。
build.gradle
Koin 2.2から WorkManager と Jetpack Compose にも対応しました。
WorkManagerやJetpack Composeを使っていないなら追加しなくてもいいと思います
(筆者はどちらも使用しないので追加していません)。
- koin_version = '2.1.6'
+ koin_version = '2.2.1'
// Koin AndroidX Scope features
implementation "org.koin:koin-androidx-scope:$koin_version"
// Koin AndroidX ViewModel features
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
// Koin AndroidX Fragment features
implementation "org.koin:koin-androidx-fragment:$koin_version"
+ // Koin AndroidX WorkManager
+ implementation "org.koin:koin-androidx-workmanager:$koin_version"
+ // Koin AndroidX Jetpack Compose
+ implementation "org.koin:koin-androidx-compose:$koin_version"
// Koin AndroidX Experimental features
implementation "org.koin:koin-androidx-ext:$koin_version"
KoinComponentの修正1:パッケージ名の修正
2.1.6では KoinComponent
インタフェースは org.koin.core
に属していましたが2.2.1では org.koin.core.component
に属しています。
import文を修正します。
- import org.koin.core.KoinComponent
+ import org.koin.core.component.KoinComponent
- import org.koin.core.inject
+ import org.koin.core.component.inject
- import org.koin.core.get
+ import org.koin.core.component.get
- import org.koin.core.bind
+ import org.koin.core.component.bind
KoinComponent
が警告扱いになっているのが気になる方は「おまけ・KoinComponentの修正2:KoinComponent誤使用のチェックと警告の抑制」を参照してください。
stateViewModel関係の修正
2.1.6ではViewModel
に SavedStateHandle
を持たせる場合――俗に言うStateViewModelを取り扱う場合、stateViewModel
や getStateViewModel
といった拡張関数を使っていました。
2.2.1ではこれらは viewModel
、 getViewModel
に統合されました。
注入するインスタンスを生成する箇所
2.1.6では viewModel
のdefinitionとして (handle: SavedStateHandle) ->
のようにSavedStateHandleインスタンスを指定したうえでViewModelクラスにhandle
を渡していました。
2.2.1では (handle: SavedStateHandle) ->
は不要になりました。代わりに、ViewModelクラスへの注入は get
を使用します。
- viewModel { (handle: SavedStateHandle) ->
- FooViewModel(
- handle, ...
- )
- }
+ viewModel {
+ FooViewModel(
+ get(), ...
+ )
+ }
注入する箇所
stateViewModel
-> viewModel
、 getStateViewModel
-> getViewModel
に変更します。
- import org.koin.androidx.viewmodel.ext.android.getStateViewModel
+ import org.koin.androidx.viewmodel.ext.android.getViewModel
...
- foo = getStateViewModel()
+ foo = getViewModel()
- import org.koin.androidx.viewmodel.ext.android.stateViewModel
+ import org.koin.androidx.viewmodel.ext.android.viewModel
...
- private val fooViewModel: FooViewModel by stateViewModel()
+ private val fooViewModel: FooViewModel by viewModel()
KoinContextHandlerの修正
KoinContextHandler
がdeprecatedになりました。
代替は GlobalContext
です。
- import org.koin.core.context.KoinContextHandler
+ import org.koin.core.context.GlobalContext
- KoinContextHandler.getOrNull() ?: startKoin {
+ GlobalContext.getOrNull() ?: startKoin {
...
}
おまけ・KoinComponentの修正2:KoinComponent誤使用のチェックと警告の抑制
tl;dr
-
KoinComponent
が警告扱いになったけどdeprecatedになったわけではない - 警告を抑制したい場合は
@Suppress("EXPERIMENTAL_API_USAGE")
をつけていけばよい -
KoinComponent
の使用が推奨されるのは以下のパターン- ユーザーがコンストラクターからインスタンス生成することができない場合
- Koin開発者やKoinを拡張したい人が使う場合
警告扱いになった経緯
2.2.1にするとAndroid Studio上で KoinComponent
が警告表示されるようになりました。
簡単に言うと お前らの KoinComponent
の使い方は間違っている から おいそれと KoinComponent
を使わせないように警告表示するようにした のが経緯のようです。
この対応が良いかどうかの議論は置いておいておくとして、とりあえず KoinComponent
がdeprecatedになったわけではないのでこのまま使い続けて大丈夫そうです。
警告を抑制する方法
警告を抑制したい場合は KoinComponent
を使っているクラスに @Suppress("EXPERIMENTAL_API_USAGE")
を付与して警告を握りつぶすのがよいと思います。
@KoinApiExtension
をつける方がKoin開発者の意図に近いかもしれませんが、@KoinApiExtension
だとそのクラスを使っているクラスにさかのぼって警告が出るようになります。
プロジェクトの規模やクラス同士の関係にもよりますが、使っているクラス全部に @KoinApiExtension
を付与して回る手間は割けないと思います。
正しく使っているかどうかのチェック
とはいえ、Koin開発者の指摘を全く無視するのもよくありません。
以降では KoinComponent
が正しく使われているかどうかを例示します。
あなたのプロジェクトで当てはまるかどうかチェックしてみてください。
KoinComponent
が誤って使われている例
はじめに KoinComponent
が誤って使われる例です。
この例の最もよくあるパターンの1つはActivityやFragmentだと思います。
import org.koin.component.inject
import org.koin.component.get
// THIS CODE IS WRONG!!
class FooActivity: AppCompatActivity, KoinComponent {
private val fooModule by inject<FooModule>()
private lateinit var barModule
fun barInit() {
barModule = get()
}
}
import org.koin.component.inject
import org.koin.component.get
// THIS CODE IS WRONG!!
class FooFragment: Fragment, KoinComponent {
private val fooModule by inject<FooModule>()
private lateinit var barModule
fun barInit() {
barModule = get()
}
}
Kotlinでは ComponentCallbacks
に拡張関数を用意しています。
ActivityやFragmentはどちらも ComponentCallbacks
を継承しているためKoinComponentの継承は不要です。
- import org.koin.component.inject
- import org.koin.component.get
+ import org.koin.android.ext.android.inject
+ import org.koin.android.ext.android.get
- class FooActivity: AppCompatActivity, KoinComponent {
+ class FooActivity: AppCompatActivity {
private val fooModule by inject<FooModule>()
private lateinit var barModule
fun barInit() {
barModule = get()
}
}
- import org.koin.component.inject
- import org.koin.component.get
+ import org.koin.android.ext.android.inject
+ import org.koin.android.ext.android.get
- class FooFragment: Fragment, KoinComponent {
+ class FooFragment: Fragment {
private val fooModule by inject<FooModule>()
private lateinit var barModule
fun barInit() {
barModule = get()
}
}
Android Studioで開発していると ⌘ + Enter
(Windowsでは Alt + Enter
)で補完させることが多いのでこういったミスは出やすいですね。
他の例だとコンストラクター引数を使っていないクラスでしょう。
import org.koin.component.inject
// THIS CODE IS NOT RECOMMENDED!!
class FooRepository: KoinComponent {
private val fooDao by inject<FooDao>()
}
FooRepository
は KoinComponent
を除いて何も継承していないプレーンなクラスです。
上記のコードでは inject
を使って FooDao
インスタンスを注入していますが、このような場合 FooDaoインスタンス
はKoinに頼らず普通にコンストラクター引数から与えるようにしてください。そして Koinの module
関数内でFooRepositoryおよびFooDaoを生成するようにしてください。
- import org.koin.component.inject
- class FooRepository: KoinComponent {
- private val fooDao by inject<FooDao>()
- }
+ class FooRepository(private val fooDao: FooDao) {
+ }
...
module {
+ single {
+ FooRepository(fooDao = get())
+ }
+
+ single {
+ FooDao()
+ }
}
KoinComponent
を使わざるをえない例
つぎは、 KoinComponent
を使わざるをえない例です。この例ではコードの修正は必要ありません。
import androidx.room.Dao
import org.koin.component.KoinComponent
import org.koin.component.inject
@Dao
interface FooDao: KoinComponent {
private val fooConfig by inject<FooConfig>()
...
}
上記は Android Room を使ったコードです。RoomではDAOインスタンスをユーザーが直接インスタンス生成することはできません。
このように、 ユーザーがコンストラクターからインスタンス生成できないクラスにおいてはKoinComponentを使うのが正しい やり方となります。
警告にびびらずどんどん使っていきましょう。
おまけのおまけ・WorkManagerの対応
2.1.6では WorkManager のWorkクラスも KoinComponent
を使わないと注入が実現できませんでした。
2.2.1だとWorkManagerに対応したので KoinComponent
は不要になった……らしいのですが、
-
公式のマニュアル の記述が怪しい。
setupWorkManagerFactory
関数が存在しない(CHANGELOGではデフォルトの処理を用意したから要らなくなったみたいに読める記述はありますが……) - WorkManagerに関連するテストコードが全部コメントアウトされている
- 正式リリースっぽい雰囲気だが実はまだ実験段階(
@KoinExperimentalAPI
がついている)
(全て2.2.1時点です)
ということで、筆者はまだ導入せずWorkクラスは KoinComponent
を使った注入コードのままにしてあります。
どなたか試された方がいたら教えて下さい。