補足 (2021-02-20)
この記事の内容は
Dagger + Hilt を使用したDI
を行う場合は関係ない内容になっています。
DIライブラリを使用せずViewModel
へのコンストラクタインジェクションだけを軽く行いたい場合(限定的すぎる?)などにちょっと楽をする方法、くらいに見てもらればと思います。
追記 (2020-12-27)
ViewModelOwner#lazyProvideViewModel
を追加。
▼
前提1
ViewModel
の最もシンプルな作り方は多分こんな感じだろう。
class HogeViewModel : ViewModel() {
// LiveDataなど色々
}
class HogeActivity {
private val viewModel : HogeViewModel by lazy {
ViewModelProvider(this)[HogeViewModel::class.java]
}
// 他省略
}
ViewModelProvider
はViewModelStoreOwner
とViewModelProvider.Factory
を引数にとり、後者を省略すると無引数コンストラクタでViewModel
が生成される。
後半の[]
の中身は、(省略可能な)キー名
と生成するViewModelのクラス
を指定する。キー
はowner
が同型のViewModel
を複数持つ場合にそれぞれを識別するために使う。
前提2
以上の方法ではViewModel
が引数有りコンストラクタを用意することができないため、外部から初期値を与えたい場合などは対象のプロパティをミュータブルにせざるを得ない問題がある。
きっちりイミュータブルにしておきたい場合は、次のようにインスタンス生成用のFactory
を用意する。
class HogeViewModel(
private val hogeArg : Hoge
) : ViewModel() {
// ------ //
class Factory(
private val hogeArg : Hoge
) : ViewModelProvider.NewInstanceFactory() {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return HogeViewModel(hogeArg) as T
}
}
}
class HogeActivity {
private val viewModel : HogeViewModel by lazy {
val hoge = Hoge()
val factory = HogeViewModel.Factory(hoge)
ViewModelProvider(this, factory)[HogeViewModel::class.java]
}
// 他省略
}
HogeActivity
側でHogeViewModel
生成時にFactory
を渡すことで、Factory#create()
メソッドによってインスタンスが生成される。
簡単化
新しいViewModel
を作るごとにこのFactory
を毎回書くのは少々面倒であるし無駄に冗長なので、Factory
を作る関数と、作ったFactory
を使ってViewModel
インスタンスを生成する関数を作って簡単にする。
package com.suihan74.utilities
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import kotlin.reflect.KClass
class ViewModelFactory<ViewModelT : ViewModel>(
private val creator: () -> ViewModelT,
private val kClass: KClass<ViewModelT>
) : ViewModelProvider.NewInstanceFactory() {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return creator.invoke() as T
}
/** ViewModelを作成・取得する */
fun provide(owner: ViewModelStoreOwner, key: String? = null) : ViewModelT =
if (key == null) ViewModelProvider(owner, this)[kClass.java]
else ViewModelProvider(owner, this)[key, kClass.java]
}
/**
* ViewModel作成時に用いるNewInstanceFactoryを生成する
*/
inline fun <reified ViewModelT : ViewModel> createViewModelFactory(
noinline creator: ()->ViewModelT
) = ViewModelFactory(creator, ViewModelT::class)
/**
* ViewModelを作成・取得する
*/
inline fun <reified ViewModelT : ViewModel> provideViewModel(
owner: ViewModelStoreOwner,
noinline creator: ()->ViewModelT
) = createViewModelFactory(creator).provide(owner)
/**
* ViewModelを作成・取得する
*/
inline fun <reified ViewModelT : ViewModel> provideViewModel(
owner: ViewModelStoreOwner,
key: String?,
noinline creator: ()->ViewModelT
) = createViewModelFactory(creator).provide(owner, key)
// ------ //
/**
* ViewModelを作成・取得する(lazy版)
*/
inline fun <reified ViewModelT : ViewModel> ViewModelStoreOwner.lazyProvideViewModel(
noinline creator: ()->ViewModelT
) = lazy { provideViewModel(this, creator) }
/**
* ViewModelを作成・取得する(lazy版)
*/
inline fun <reified ViewModelT : ViewModel> ViewModelStoreOwner.lazyProvideViewModel(
key: String?,
noinline creator: ()->ViewModelT
) = lazy { provideViewModel(this, key, creator) }
以上の内容のファイルを用意しておいて、次のように使う。
class HogeViewModel(
private val hoge: Hoge
) : ViewModel() {
// 省略
}
class HogeActivity {
private val viewModel : HogeViewModel by lazy {
provideViewModel(this) {
val hoge = Hoge()
HogeViewModel(hoge)
}
}
// 他省略
}
キー
を使う場合はprovideViewModel(this, key) { // --- // }
とする。
これで少しはすっきりしたというものです。
よかったですね。
lazyProvideViewModelを追加 (2020-12-27)
private val viewModel by lazy {
provideViewModel(this) {
HogeViewModel()
}
}
をもう少し簡単に書くためにlazyProvideViewModel
関数を追加した。
private val viewModel by lazyProvideViewModel {
HogeViewModel()
}
使用できるViewModelStoreOwner
がthis
限定である(他のowner
を渡せない)という制限がある点には注意。
これはFragment
のプロパティ初期化などでActivity
などをowner
に指定するとFragment
インスタンス生成時にはまだアタッチ前なので失敗する問題があるため、このようにしている。