12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AndroidAdvent Calendar 2020

Day 8

Koinのバージョンを2.1.6から2.2.1に上げる

Last updated at Posted at 2020-12-07

はじめに

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から WorkManagerJetpack Compose にも対応しました。
WorkManagerやJetpack Composeを使っていないなら追加しなくてもいいと思います
(筆者はどちらも使用しないので追加していません)。

build.gradle

- 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ではViewModelSavedStateHandle を持たせる場合――俗に言うStateViewModelを取り扱う場合、stateViewModelgetStateViewModel といった拡張関数を使っていました。
2.2.1ではこれらは viewModelgetViewModel に統合されました。

注入するインスタンスを生成する箇所

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 -> viewModelgetStateViewModel -> 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>()
}

FooRepositoryKoinComponent を除いて何も継承していないプレーンなクラスです。
上記のコードでは 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 を使った注入コードのままにしてあります。

どなたか試された方がいたら教えて下さい。

12
8
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
12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?