KoinでのDI時、パラメータを動的に変更する方法の紹介です。
Daggerに慣れている人向けに解説しますと、Injectする引数を可変にしたい場合、DaggerではAssistedInject
を使うことになると思います。
これをKoinでどうやるか、という話になります。
Koinについてはこちらから。
パラメータ宣言
公式的には以下のドキュメントで紹介されています。
これを実際に書いて、多少複雑な使い方を紹介していきます。
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(...)
で名前を付けることが出来ます。
同じ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でパターンを定義してしまうのが良いと思います。
ただし、パターンとして紹介しておいてなんですが、ここまでやると仕組みとして複雑化しそうなので注意が必要です。