Android Jetpack Compose切换Tab时ViewModel状态重置问题求助
解决Jetpack Compose底部导航切换时ViewModel状态重置问题
核心原因
当使用hiltViewModel()在单个Compose Screen中注入ShopListHomeViewModel时,默认作用域是当前导航目的地的NavBackStackEntry。底部导航切换时,非活跃的目的地会被从回退栈移除,对应的NavBackStackEntry销毁,ViewModel也随之被回收,导致状态丢失、页面重新加载。
解决方案
1. 改用根导航图作用域的ViewModel
将ViewModel的作用域提升至底部导航所在的根导航图,确保切换Tab时ViewModel不会被销毁:
// 在ShopListHomeScreen中获取根导航图作用域的ViewModel @Composable fun ShopListHomeScreen(navController: NavController) { // 替换R.id.root_nav_graph为你的根导航图ID val rootBackStackEntry = navController.getBackStackEntry(R.id.root_nav_graph) val viewModel = hiltViewModel<ShopListHomeViewModel>(rootBackStackEntry) // 页面逻辑... }
或者在NavHost的父Compose中提前获取ViewModel,再传递给Screen:
@Composable fun MainScreen(navController: NavController) { val rootBackStackEntry = navController.getBackStackEntry(R.id.root_nav_graph) val shopListViewModel = hiltViewModel<ShopListHomeViewModel>(rootBackStackEntry) NavHost(navController = navController, startDestination = "shop_list_home") { composable("shop_list_home") { ShopListHomeScreen(viewModel = shopListViewModel) } // 其他底部导航目的地... } }
2. 用SavedStateHandle持久化关键状态
如果需要即使ViewModel意外销毁也能恢复核心数据,可在ViewModel中注入SavedStateHandle存储状态:
@HiltViewModel class ShopListHomeViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle, private val shopListRepository: ShopListRepository ) : ViewModel() { // 从SavedStateHandle加载缓存数据,默认空列表 private val _shopLists = savedStateHandle.getStateFlow( key = "shop_lists", initialValue = emptyList<ShopList>() ) val shopLists = _shopLists.asStateFlow() // 仅在下拉刷新或创建新列表时主动加载数据 fun reloadShopLists() { viewModelScope.launch { val newData = shopListRepository.fetchShopLists() savedStateHandle["shop_lists"] = newData } } // 页面初始化时,仅当缓存为空才加载数据 fun initLoad() { if (_shopLists.value.isEmpty()) { reloadShopLists() } } }
3. 标记底部导航目的地为顶级目的地
在导航图XML中,将底部导航的所有目的地标记为topLevelDestination="true",避免切换时被从回退栈移除:
<navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/root_nav_graph" app:startDestination="@id/shop_list_home"> <composable android:id="@+id/shop_list_home" android:name="com.example.shoplist.ShopListHomeScreen" app:route="shop_list_home" app:topLevelDestination="true"/> <!-- 其他底部导航目的地同样设置topLevelDestination --> </navigation>
验证步骤
- 在ViewModel的
onCleared()方法中添加日志,切换Tab时检查是否被调用。如果未调用,说明作用域设置正确。 - 测试下拉刷新和创建新列表功能,确认
reloadShopLists()能触发数据更新。 - 切换Tab返回后,确认页面直接显示缓存数据,无重新加载状态。
内容的提问来源于stack exchange,提问作者Varian Wrynn
相关产品推荐
相关产品推荐

