■ 概要
ViewModel にコンストラクタ引数が存在する場合に ViewModelProvider.Factory の実装を毎回作るのがめんどくさいので、それを省略する。
■ Before
こういうのがめんどくさい
// Factory を呼ぶのがめんどい。
// Lazy で受け取る必要性がない。
val viewModel by viewModels<XxxActivityViewModel> { Factory(arg1) }
// Factory を実装するのがめんどい。
class Factory(private val arg1: String) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
XxxActivityViewModel(arg1) as T
}
■ After
こんな感じでらくちん。
val viewModel = viewModels(::XxxActivityViewModel, arg1)
■ ライブラリ的なもの
import androidx.activity.ComponentActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.Factory
import kotlin.reflect.KFunction1
// コンストラクタ引数が1つの場合の関数。
// - 静的な型チェックをしたほうがいいと思うので、引数分だけ関数を作るのがよさげ。
// - ()->Unit の lambda 1 つで済ませる方法のほうが万能だが、おいら的には微妙。
// lamda の括弧が目に入った瞬間に脳のリソースが多めに消費されるような気がする。
// - 以前は Lazy<VM> を返すものを作ってみたが、そもそもライフサイクルのある Activity や
// Fragment の member として配置するようなバギーなことをさせないためにも、あえて現物を返すようにした。
@Suppress("UNCHECKED_CAST")
inline fun <reified VM : ViewModel, A1> ComponentActivity.viewModels(factoryFunction: KFunction1<A1, VM>, arg1: A1): VM {
val factory = object : Factory {
override fun <VM : ViewModel?> create(modelClass: Class<VM>): VM = factoryFunction(arg1) as VM
}
return ViewModelProvider(viewModelStore, factory).get(VM::class.java)
}
■ まとめ
- ViewModel のコンストラクタ引数が存在する場合で、ViewModelProvider.Factory の実装が単純にコンストラクタを呼んでいるだけなのであれば、こんな感じでやっても良さそうな気がする。
- Lazy で返せるようにしておくと、Activity の member として
val viewModel
のように定義される可能性が増す。onCreate() 内部で初めてアクセスされる分には問題なく動作するが、別の member 初期化時に viewModel を取得してしまったりして onCreate() 以外の箇所で ViewModel の instance が生成されるようなことが起き得る。また、intent の情報等をコンストラクタに渡す必要があるような場合にコンパイルエラーとして検出できないバグになるようなこともある。 - 引数を
()-> Unit
とすれば lambda 1 つで済ませられる1が、おいら的には微妙。lamda の括弧が目に入った瞬間に脳のリソースが多めに消費されるような気がする2。 - vararg と reflection を使って引数を可変にすることもできるけど、静的に型チェックできたほうが保守性が高いと思うので、引数分だけ関数を作るのが無難な気がしている今日この頃。
ということで、おしまい。