背景
教程ViewModel(一)提到过如何在Nav3中使用ViewModel。总结起来就是使用lifecycle-viewmodel-navigation3库中提供的API来注入ViewModel。但是实际会遇到一个问题,就是多页面之间无法共享ViewModel,导致页面间的逻辑、数据无法复用。
共享ViewModel
要解决多页面间的共享ViewModel,实际要解决ViewModelStore共享问题。因为在构造ViewModel时,会将对象临时保存到ViewModelStore上,下一次获取相同的ViewModel时,直接从ViewModelStore上获取缓存,无需构建新对象。
如何共享ViewModelStore,无非就是解决以下问题:
- 需要定义一个什么样的数据结构保存,缓存已经构建的ViewModelStore?
- 如何在每次在构建ViewModel,可以按需是否从缓存中,获取共享的ViewModelStore?
- 页面退出时,是否要销毁当前缓存中的ViewModelStore?
官方解决案例源码,可供参考。源码链接
package com.example.nav3recipes.sharedviewmodel
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.lifecycle.HasDefaultViewModelProviderFactory
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.SAVED_STATE_REGISTRY_OWNER_KEY
import androidx.lifecycle.SavedStateViewModelFactory
import androidx.lifecycle.VIEW_MODEL_STORE_OWNER_KEY
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.enableSavedStateHandles
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.MutableCreationExtras
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import androidx.navigation3.runtime.NavEntry
import androidx.navigation3.runtime.NavEntryDecorator
import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.compose.LocalSavedStateRegistryOwner
/**
* Returns a [SharedViewModelStoreNavEntryDecorator] that is remembered across recompositions.
*
* @param [viewModelStoreOwner] The [ViewModelStoreOwner] that provides the [ViewModelStore] to
* NavEntries
* @param [removeViewModelStoreOnPop] A lambda that returns a Boolean for whether the store for a
* [NavEntry] should be removed when the [NavEntry] is popped from the backStack. If true, the
* entry's ViewModelStore will be removed.
*/
@Composable
fun <T : Any> rememberSharedViewModelStoreNavEntryDecorator(
viewModelStoreOwner: ViewModelStoreOwner =
checkNotNull(LocalViewModelStoreOwner.current) {
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
},
removeViewModelStoreOnPop: () -> Boolean = { true },
): SharedViewModelStoreNavEntryDecorator<T> {
val currentRemoveViewModelStoreOnPop = rememberUpdatedState(removeViewModelStoreOnPop)
return remember(viewModelStoreOwner, currentRemoveViewModelStoreOnPop) {
SharedViewModelStoreNavEntryDecorator(
viewModelStoreOwner.viewModelStore,
removeViewModelStoreOnPop,
)
}
}
/**
* Provides the content of a [NavEntry] with a [ViewModelStoreOwner] and provides that
* [ViewModelStoreOwner] as a [LocalViewModelStoreOwner] so that it is available within the content.
*
* If the [NavEntry] specifies that it has a parent in its metadata, the parent's
* [ViewModelStoreOwner] will be supplied instead of creating a new one. This allows the
* entry to access its parent's [ViewModel]s.
*
* @see [SharedViewModelStoreNavEntryDecorator.parent]
*
* This requires the usage of [androidx.navigation3.runtime.SaveableStateHolderNavEntryDecorator] to
* ensure that the [NavEntry] scoped [ViewModel]s can properly provide access to
* [androidx.lifecycle.SavedStateHandle]s
*
* @param [viewModelStore] The [ViewModelStore] that provides to NavEntries
* @param [removeViewModelStoreOnPop] A lambda that returns a Boolean for whether the store for a
* [NavEntry] should be cleared when the [NavEntry] is popped from the backStack. If true, the
* entry's ViewModelStore will be removed.
* @see NavEntryDecorator.onPop for more details on when this callback is invoked
*/
class SharedViewModelStoreNavEntryDecorator<T : Any>(
viewModelStore: ViewModelStore,
removeViewModelStoreOnPop: () -> Boolean,
) :
NavEntryDecorator<T>(
onPop = ({ key ->
if (removeViewModelStoreOnPop()) {
viewModelStore.getEntryViewModel().clearViewModelStoreOwnerForKey(key)
}
}),
decorate = { entry ->
// If the entry indicates it has a parent, use its parent's ViewModelStore.
val contentKey = entry.metadata[PARENT_CONTENT_KEY] ?: entry.contentKey
val viewModelStore =
viewModelStore.getEntryViewModel().viewModelStoreForKey(contentKey)
val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current
val childViewModelStoreOwner = remember {
object :
ViewModelStoreOwner,
SavedStateRegistryOwner by savedStateRegistryOwner,
HasDefaultViewModelProviderFactory {
override val viewModelStore: ViewModelStore
get() = viewModelStore
override val defaultViewModelProviderFactory: ViewModelProvider.Factory
get() = SavedStateViewModelFactory()
override val defaultViewModelCreationExtras: CreationExtras
get() =
MutableCreationExtras().also {
it[SAVED_STATE_REGISTRY_OWNER_KEY] = this
it[VIEW_MODEL_STORE_OWNER_KEY] = this
}
init {
require(this.lifecycle.currentState == Lifecycle.State.INITIALIZED) {
"The Lifecycle state is already beyond INITIALIZED. The " +
"SharedViewModelStoreNavEntryDecorator requires adding the " +
"SavedStateNavEntryDecorator to ensure support for " +
"SavedStateHandles."
}
enableSavedStateHandles()
}
}
}
CompositionLocalProvider(LocalViewModelStoreOwner provides childViewModelStoreOwner) {
entry.Content()
}
},
) {
companion object {
private const val PARENT_CONTENT_KEY = "shared_decorator_parent_content_key"
/**
* Use this function to specify a `NavEntry`'s parent. The parent's
* `ViewModelStoreOwner` will be supplied using `LocalViewModelStoreOwner` rather than
* creating a new `ViewModelStoreOwner` for this `NavEntry`.
*/
fun parent(contentKey: Any) = mapOf(PARENT_CONTENT_KEY to contentKey)
}
}
private class EntryViewModel : ViewModel() {
private val owners = mutableMapOf<Any, ViewModelStore>()
fun viewModelStoreForKey(key: Any): ViewModelStore = owners.getOrPut(key) { ViewModelStore() }
fun clearViewModelStoreOwnerForKey(key: Any) {
owners.remove(key)?.clear()
}
override fun onCleared() {
owners.forEach { (_, store) -> store.clear() }
}
}
private fun ViewModelStore.getEntryViewModel(): EntryViewModel {
val provider =
ViewModelProvider.create(
store = this,
factory = viewModelFactory { initializer { EntryViewModel() } },
)
return provider[EntryViewModel::class]
}
上面案例可以精细控制ViewModel的生命周期。比如
- 当前ViewModel是否能被其他页面共享,共享范围以页面单位计算。
- 若要将ViewModel设置为全局共享页面,只需要将ViewModel注入到首页。后面的子页面,都能全局共享此ViewModel
- 若要移除ViewModel,只需要将相关联的页面全部关闭即可