[Android] Koinの使い方

久しぶりに`Koin`を触ったので、メモメモ。

# Koinを導入

まず`Koin`を導入します。

導入については [こちら](https://insert-koin.io/docs/setup/v3) を参考にしました。

```build.gradle
buildscript {
    // 追加
    ext.koin_version = '3.1.2'
    ext.kotlin_version = '1.5.20'
    repositories {
        google()
	// なければ追加する
        mavenCentral()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:7.0.0"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
```

```app/build.gradle
dependencies {
    ...
    // 追加
    implementation "io.insert-koin:koin-android:$koin_version"
}
```

[Setup Koin v3](https://insert-koin.io/docs/setup/v3) に記載されていますが、`jcenter`の閉鎖で、koinプロジェクトのIDが`org.koin`から`io.insert-koin`に変更されたとのことです。

> Due to Jcenter shutdown, the koin project's maven group id was previously org.koin and is now io.insert-koin. Please check your configuration with modules below.

# `Application`クラスで初期化する
アプリが起動するときに最初に呼び出される`Application`を継承したクラスで、`Koin`の初期化を行います。

```MyApp.kt
package com.example.sample

import android.app.Application
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin

class MyApp : Application() {

    override fun onCreate() {
        super.onCreate()
        setupKoin()
    }

    private fun setupKoin() {

        startKoin {
            androidContext(this@MyApp)
            modules(modules)
        }
    }
}
```

```AppModule.kt
package com.example.sample

import com.example.sample.model.repository.Repository
import com.example.sample.model.repository.RepositoryImpl
import com.example.sample.ui.MainViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module

val modules = module {
    // interfaceでDIしたいときは、ジェネリクスにinterface、{ } に継承したクラスを定義する。
    single<Repository> { RepositoryImpl() }
    viewModel { MainViewModel() }
    // AndroidViewModelを継承している場合、コンストラクタでApplicationが必要になるので「get()」を使う
    viewModel { SampleViewModel(get()) }
}
```
モジュール宣言を別ファイルに切り出しています。各宣言については以下のような動きになります。

- single: シングルトンでモジュール宣言
- factory: モジュール宣言したクラスが必要になるたび、インスタンスを生成する
- viewModel: ViewModelのモジュール宣言で使用する

```AndroidManifest.xml
<application
        android:name=".MyApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true">
        <activity
            android:name=".ui.MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
```

`android:name`に`MyApp.kt`を設定して、アプリ起動時に呼び出されるようにします。

# DIする

実際にDIしてみます。

```MainActivity.kt
class MainActivity : AppCompatActivity() {

    // MainViewModelをDIする
    private val _viewModel: MainViewModel by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        ...
    }
}
```

```MainViewModel.kt
class MainViewModel : ViewModel() {

    private val _repository: Repository by inject(Repository::class.java)

    init {
        fetchRepositories()
    }

    private fun fetchRepositories(userName: String = "user") {

        viewModelScope.launch(Dispatchers.IO) {
            val result = _repository.fetchRepositories(userName)
            ...
        }
    }
}
```