補足 (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インスタンス生成時にはまだアタッチ前なので失敗する問題があるため、このようにしている。
