kako.dev

開発、自作アプリのこと

hiltViewModel() vs viewModel()

Jetpack ComposeにViewModelをDIする方法として2通り見つけた。

  • androidx.hilt.navigation.compose.hiltViewModel
  • androidx.lifecycle.viewmodel.compose.viewModel

ググって出てきた記事ではhiltViewModelを使っているケースが多い気がしたがこれはnavigation.composeの中にあるし、公式にはviewmodel.compose.viewModelを使っているパターンが紹介されている(*) しで、どちらがいいのかわからないのでちょっと調べてみた。

hiltViewModel

コード中のコメントには以下のように書かれていて既存のHiltViewModelがあればそれを返してくれるのかな??

/* * Returns an existing * HiltViewModel * -annotated [ViewModel] or creates a new one scoped to the current navigation graph present on * the {@link NavController} back stack. * * If no navigation graph is currently present then the current scope will be used, usually, a * fragment or an activity. * * @sample androidx.hilt.navigation.compose.samples.NavComposable * @sample androidx.hilt.navigation.compose.samples.NestedNavComposable /

実装

実装はこんな風になっててViewModelStoreOwnerが気になる。 checkNotNullでLocalViewModelStoreOwner.currentが存在しないと例外を投げるみたいだ。

if (viewModelStoreOwner is NavBackStackEntry) を見ているので、バックスタックに存在していればそれを返してくれるイメージなのかな。

@Composable
inline fun <reified VM : ViewModel> hiltViewModel(
    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
        "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
    }
): VM {
    val factory = createHiltViewModelFactory(viewModelStoreOwner)
    return viewModel(viewModelStoreOwner, factory = factory)
}

@Composable
@PublishedApi
internal fun createHiltViewModelFactory(
    viewModelStoreOwner: ViewModelStoreOwner
): ViewModelProvider.Factory? = if (viewModelStoreOwner is NavBackStackEntry) {
    HiltViewModelFactory(
        context = LocalContext.current,
        navBackStackEntry = viewModelStoreOwner
    )
} else {
    // Use the default factory provided by the ViewModelStoreOwner
    // and assume it is an @AndroidEntryPoint annotated fragment or activity
    null
}

viewModel

コード中のコメントには以下のように書かれていてこれも既存のものがあれば返してくれるようなことが書いてある

Returns an existing ViewModel or creates a new one in the given owner (usually, a fragment or an activity), defaulting to the owner provided by LocalViewModelStoreOwner. The created ViewModel is associated with the given viewModelStoreOwner and will be retained as long as the owner is alive (e.g. if it is an activity, until it is finished or process is killed). Params: viewModelStoreOwner - The owner of the ViewModel that controls the scope and lifetime of the returned ViewModel. Defaults to using LocalViewModelStoreOwner. key - The key to use to identify the ViewModel. factory - The ViewModelProvider.Factory that should be used to create the ViewModel or null if you would like to use the default factory from the LocalViewModelStoreOwner Returns: A ViewModel that is an instance of the given VM type.

実装

こっちもViewModelStoreOwnerがいる。

こっちはバックスタックとか関係なくViewModelStoreOwnerに紐づいるものが存在していればそれを返してくれるイメージ??

@Suppress("MissingJvmstatic")
@Composable
public inline fun <reified VM : ViewModel> viewModel(
    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
        "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
    },
    key: String? = null,
    factory: ViewModelProvider.Factory? = null
): VM = viewModel(VM::class.java, viewModelStoreOwner, key, factory)


@Suppress("MissingJvmstatic")
@Composable
public fun <VM : ViewModel> viewModel(
    modelClass: Class<VM>,
    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
        "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
    },
    key: String? = null,
    factory: ViewModelProvider.Factory? = null
): VM = viewModelStoreOwner.get(modelClass, key, factory)

private fun <VM : ViewModel> ViewModelStoreOwner.get(
    javaClass: Class<VM>,
    key: String? = null,
    factory: ViewModelProvider.Factory? = null
): VM {
    val provider = if (factory != null) {
        ViewModelProvider(this, factory)
    } else {
        ViewModelProvider(this)
    }
    return if (key != null) {
        provider.get(key, javaClass)
    } else {
        provider.get(javaClass)
    }
}

使い分けイメージ

  • 画面単位で同じViewModelのインスタンスを返して欲しいならhiltViewModel
  • ことなる画面でも同じViewModelのインスタンスを共有したいならviewModel

という理解でいいんだろうか。

もう少し余裕があればもっと深く調べてみたい。

ハマりポイント

hilt-composeライブラリをimplementationしたらRecords requires ASM8というエラーが出た 詳細を見るとFailed to transform moshi-1.13.0.jarとのこと

// When using Kotlin.
    kapt "androidx.hilt:hilt-compiler:1.0.0"
    implementation "androidx.hilt:hilt-navigation-compose:1.0.0-rc01"
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0"

ググるとDaggerにIssueが載っていて解決法として下記を発見。 https://github.com/square/moshi/issues/1463#issuecomment-994576201

正しいのかわからないが、ワークアラウンドとして gradle.propertiesに下記を追記すると解決。

android.jetifier.ignorelist=moshi-1.13.0

https://developer.android.com/jetpack/compose/libraries#hilt