Jetpack Compose切换路由后ViewModel isActive仍为true的问题解决
问题:底部导航切换后ViewModel后台循环无法停止
我开发了一款带底部导航栏的应用,包含多个标签页。其中标签页A(HomeScreen)关联的HomeViewModel在初始化时会启动一个每5秒执行一次的循环,用于获取加密货币BTC的最新价格并更新界面。但切换到标签页B(CryptoToFiatScreen)后,HomeViewModel中的isActive始终为true,循环持续执行。希望ViewModel在所属路由不在前台时停止循环,但保留ViewModel实例,原本以为将ViewModel作用域限定到路由后切换路由会触发isActive变为false,实际并未生效。
当前代码
NavGraph
NavHost(navController, startDestination = BottomNavItem.HomeScreen.screen_route) { composable(BottomNavItem.HomeScreen.screen_route) { val homeViewModel: HomeViewModel = hiltViewModel() val homeUiState = homeViewModel.uiState.collectAsState() HomeScreen(uiState = homeUiState.value) } composable(BottomNavItem.FiatToCryptoScreen.screen_route) { val viewModel: CryptoToFiatViewModel = hiltViewModel() val uiState = viewModel.uiState.collectAsState() CryptoToFiatScreen(uiState = uiState.value) } }
HomeViewModel代码
@HiltViewModel class HomeViewModel @Inject constructor(private val repo: HomeRepository) : ViewModel() { init { updateFeaturedCoin() } private fun updateFeaturedCoin() { viewModelScope.launch { while (isActive) { val featuredCoinPrice = repo.getTickerForCoin("BTC") if (featuredCoinPrice.isSuccess) { homeScreenState.update { it.copy( isLoading = false, featuredCoinPrice = featuredCoinPrice.getOrNull()?.price ) } } delay(5000) } } } .... }
解决方案
问题核心是:默认情况下,Compose Navigation的hiltViewModel()会将ViewModel作用域绑定到整个NavHost,而非单个路由,所以切换标签页时ViewModel不会进入销毁状态,viewModelScope的isActive也不会变为false。要让ViewModel感知页面的前台/后台状态,可通过以下方式实现:
1. 利用DisposableEffect传递页面可见性状态
在HomeScreen中添加DisposableEffect,监听页面的进入与退出,主动通知ViewModel启停循环:
第一步:修改HomeViewModel,添加循环控制方法
@HiltViewModel class HomeViewModel @Inject constructor(private val repo: HomeRepository) : ViewModel() { private var priceUpdateJob: Job? = null // 启动价格更新循环 fun startPriceUpdates() { // 避免重复启动 if (priceUpdateJob?.isActive != true) { priceUpdateJob = viewModelScope.launch { while (isActive) { val featuredCoinPrice = repo.getTickerForCoin("BTC") if (featuredCoinPrice.isSuccess) { homeScreenState.update { it.copy( isLoading = false, featuredCoinPrice = featuredCoinPrice.getOrNull()?.price ) } } delay(5000) } } } } // 停止价格更新循环 fun stopPriceUpdates() { priceUpdateJob?.cancel() } }
第二步:在HomeScreen中添加DisposableEffect
@Composable fun HomeScreen(uiState: HomeUiState, viewModel: HomeViewModel) { // 页面进入组合时启动循环,离开组合时停止循环 DisposableEffect(Unit) { viewModel.startPriceUpdates() onDispose { viewModel.stopPriceUpdates() } } // 原有界面渲染代码 }
第三步:更新NavGraph中HomeScreen的调用
composable(BottomNavItem.HomeScreen.screen_route) { val homeViewModel: HomeViewModel = hiltViewModel() val homeUiState = homeViewModel.uiState.collectAsState() HomeScreen(uiState = homeUiState.value, viewModel = homeViewModel) }
2. 监听页面生命周期事件(更精细控制)
如果需要区分页面的ON_START/ON_STOP状态(比如App退到后台时也停止循环),可以借助LocalLifecycleOwner监听生命周期事件:
@Composable fun HomeScreen(uiState: HomeUiState, viewModel: HomeViewModel) { val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> when (event) { Lifecycle.Event.ON_START -> viewModel.startPriceUpdates() Lifecycle.Event.ON_STOP -> viewModel.stopPriceUpdates() else -> {} } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } // 原有界面渲染代码 }
内容的提问来源于stack exchange,提问作者Gastón Saillán
相关产品推荐
相关产品推荐

