LoginSignup
4
3

More than 3 years have passed since last update.

Koinで動的な引数の解決

Last updated at Posted at 2021-02-24

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を一個取りたいとします。

class TestViewModel(
    private val id: String,
    private val useCase: TestUseCase
) : ViewModel() {
    ...
}

この時、module定義は以下の様に書けます。

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

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

パラメータの取り方

by inject()by viewModel()でInjectsする時、先程宣言した引数は parametersOf(...) で追加できます。

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

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

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

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

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

任意のタイミングでInjectする

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


    lateinit var viewModel: TestViewModel

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel = getViewModel { parametersOf("paramId") }
    }

これと合わせて使えば、引数の処理タイミングも任意に変更するとこが可能です。
更に、これを by laze{...} 内で行ってしまうことも考えられます。

    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と実装で考えます。

interface TestUseCase

class TestUseCaseImplA : TestUseCase

class TestUseCaseImplB(private val id: String) : TestUseCase

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

    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する側は以下の様に書きます。

    // 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内でこうも書けます。

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

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

4
3
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
4
3