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

# はじめに

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](https://medium.com/koin-developers/whats-next-with-koin-2-2-3-0-releases-6c5464ae5e3d) という公式の記事を見つけましたが、マイグレーションの観点からは書かれていないので読み解くのにちょっと大変でした。

ということで本記事はマイグレーションの観点からの忘備録になります。新機能の紹介はしていないのでご注意ください。

# 環境

* 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](https://developer.android.com/topic/libraries/architecture/workmanager?hl=ja) と [Jetpack Compose](https://developer.android.com/jetpack/compose?hl=ja) にも対応しました。
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文を修正します。

```kotlin

- 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` を使用します。

```kotlin

- viewModel { (handle: SavedStateHandle) ->
-     FooViewModel(
-         handle, ...
-     )
- }

+ viewModel {
+    FooViewModel(
+        get(), ...
+    )
+ }

```

### 注入する箇所

`stateViewModel` -> `viewModel` 、 `getStateViewModel` -> `getViewModel` に変更します。

```kotlin
- import org.koin.androidx.viewmodel.ext.android.getStateViewModel
+ import org.koin.androidx.viewmodel.ext.android.getViewModel

...

- foo = getStateViewModel()
+ foo = getViewModel()

```

```kotlin
- 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` です。

```kotlin
- 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だと思います。

```kotlin
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()
    }
}
```

```kotlin
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の継承は不要です。

```kotlin
- 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()
    }
}
```

```kotlin
- 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`）で補完させることが多いのでこういったミスは出やすいですね。

他の例だとコンストラクター引数を使っていないクラスでしょう。

```kotlin

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を生成するようにしてください。

```kotlin
- 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` を使わざるをえない例です。この例ではコードの修正は必要ありません。

```kotlin
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](https://developer.android.com/training/data-storage/room?hl=ja) を使ったコードです。RoomではDAOインスタンスをユーザーが直接インスタンス生成することはできません。
このように、 **ユーザーがコンストラクターからインスタンス生成できないクラスにおいてはKoinComponentを使うのが正しい** やり方となります。
警告にびびらずどんどん使っていきましょう。

## おまけのおまけ・WorkManagerの対応

2.1.6では [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager?hl=ja) のWorkクラスも `KoinComponent` を使わないと注入が実現できませんでした。
2.2.1だとWorkManagerに対応したので `KoinComponent` は不要になった……らしいのですが、

* [公式のマニュアル](https://doc.insert-koin.io/#/koin-android/workmanager) の記述が怪しい。 `setupWorkManagerFactory` 関数が存在しない（CHANGELOGではデフォルトの処理を用意したから要らなくなったみたいに読める記述はありますが……）
* WorkManagerに関連するテストコードが全部コメントアウトされている
* 正式リリースっぽい雰囲気だが実はまだ実験段階（ `@KoinExperimentalAPI` がついている）

（全て2.2.1時点です）

ということで、筆者はまだ導入せずWorkクラスは `KoinComponent` を使った注入コードのままにしてあります。

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