You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose切换路由后ViewModel isActive仍为true的问题解决

问题:底部导航切换后ViewModel后台循环无法停止

我开发了一款带底部导航栏的应用,包含多个标签页。其中标签页A(HomeScreen)关联的HomeViewModel在初始化时会启动一个每5秒执行一次的循环,用于获取加密货币BTC的最新价格并更新界面。但切换到标签页B(CryptoToFiatScreen)后,HomeViewModel中的isActive始终为true,循环持续执行。希望ViewModel在所属路由不在前台时停止循环,但保留ViewModel实例,原本以为将ViewModel作用域限定到路由后切换路由会触发isActive变为false,实际并未生效。


当前代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 03:25:35