Koinで動的な引数の解決

KoinでのDI時、パラメータを動的に変更する方法の紹介です。

Daggerに慣れている人向けに解説しますと、Injectする引数を可変にしたい場合、Daggerでは`AssistedInject`を使うことになると思います。
これをKoinでどうやるか、という話になります。

Koinについてはこちらから。
- https://insert-koin.io/docs/quickstart/android

# パラメータ宣言
公式的には以下のドキュメントで紹介されています。
- https://insert-koin.io/docs/reference/koin-core/definitions#declaring-injection-parameters

これを実際に書いて、多少複雑な使い方を紹介していきます。

## module定義
例として、Fragmentに引数としてIDを渡し、それをViewModelの引数にまで引っ張りたいときを考えます。
viewModelが以下の様に可変の引数としてIDを、Injectする予定の引数としてUseCaseを一個取りたいとします。

``` kotlin
class TestViewModel(
    private val id: String,
    private val useCase: TestUseCase
) : ViewModel() {
    ...
}
```
この時、module定義は以下の様に書けます。

``` kotlin
    factory { TestUseCaseImpl() as TestUseCase }
    viewModel { (id: String) -> TestViewModel(id, get()) }
```

module内で解決出来る引数は`get()`で指定しつつ、後で指定したい引数を宣言しておけます。

# パラメータの取り方
`by inject()`や`by viewModel()`でInjectsする時、先程宣言した引数は `parametersOf(...)` で追加できます。

``` kotlin
    private val viewModel: TestViewModel by viewModel { (parametersOf("paramId")) }
```

例えばFragmentのargument経由で引数を渡していた場合、下の様に `parametersOf(...)` 内に入れてしまうことも可能です。

``` kotlin
    private val viewModel: TestViewModel by viewModel {
        parametersOf(arguments?.getString(ID) ?: "nothing")
    }
```

navArgsを使用している場合、以下の様に書くと楽かと思います。

``` kotlin
    private val args: TestArgs by navArgs()
    private val viewModel: TestViewModel by viewModel { (parametersOf(args.testId)) }
```

## 任意のタイミングでInjectする
Koinは `get {...}` や `getViewModel {...}` で任意のタイミングでInjectする事もできます。

``` kotlin

    lateinit var viewModel: TestViewModel

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel = getViewModel { parametersOf("paramId") }
    }
```
これと合わせて使えば、引数の処理タイミングも任意に変更するとこが可能です。
更に、これを `by laze{...}` 内で行ってしまうことも考えられます。

``` kotlin
    private val viewModel: TestViewModel by lazy {
        val param: String = arguments?.getString(ID)?.let {
            ... // 何らかの処理
        } ?: "nothing"
        getViewModel<TestViewModel> { parametersOf(param) }
    }

```

# 命名定義との併用
Koinでは、同タイプのInject定義を分けるために、宣言に`named(...)`で名前を付けることが出来ます。
- https://insert-koin.io/docs/reference/koin-core/definitions#definition-naming--default-bindings

同じInterfaceを持つ異なる実装を、Interfaceの型でInjectしたい場合なんかに使えます。
これとパラメータ宣言を組み合わせることも可能です。

ここからは、以下の様なUseCaseクラスのinterfaceと実装で考えます。

```kotlin
interface TestUseCase

class TestUseCaseImplA : TestUseCase

class TestUseCaseImplB(private val id: String) : TestUseCase
```

module定義内に`namaed()`指定することで、Inject定義を分けることができます。
このmodule定義内でパラメータを宣言したり、この場で指定してしまうことも可能です。

``` kotlin
    factory(named("TYPE_A")) { TestUseCaseImplA() as TestUseCase }
    factory(named("TYPE_B")) { TestUseCaseImplB("hogeB") as TestUseCase }
    factory(named("TYPE_C")) { (id:String) -> TestUseCaseImplB(id) as TestUseCase }
```

injectする側は以下の様に書きます。

```kotlin
    // TestUseCaseImplA
    private val useCase: TestUseCase by inject(named("TYPE_A"))

    // TestUseCaseImplB
    private val useCase: TestUseCase by inject(named("TYPE_B"))

    // TestUseCaseImplB("hogeC")
    private val useCase: TestUseCase by inject(named("TYPE_C")){parameterOf("id")}
        or
    useCase = get<TestUseCase>(named("TYPE_C")){parameterOf("id")}
```
もしくはmodule内でこうも書けます。

```kotlin
    viewModel { (id: String) -> TestViewModel(id, get(named("TYPE_A"))) }
```

`named()`の引数はenumも可能です。誤字対策にもなりますし、実際はenumでパターンを定義してしまうのが良いと思います。
ただし、パターンとして紹介しておいてなんですが、ここまでやると仕組みとして複雑化しそうなので注意が必要です。