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

API请求完成后跳转页面,主页面Flow状态接收异常问题排查

问题原因与解决方案

问题根源

你遇到的核心问题是ViewModel实例被销毁重建:

  • 调用hiltViewModel()时,默认会将ViewModel绑定到当前屏幕对应的NavBackStackEntry。
  • 当执行popUpTo(Screens.SplashScreen.route) { inclusive = true }时,SplashScreen的NavBackStackEntry会被彻底移除,绑定的MainViewModel实例也会随之销毁。
  • MainScreen创建时会生成全新的ViewModel,其statusFlow初始值为Loading,自然无法接收到SplashScreen中已完成的API请求结果。

解决方案

方法一:让ViewModel绑定到根NavGraph生命周期(推荐)

将ViewModel的作用域提升到根NavGraph,这样即使SplashScreen的BackStackEntry被移除,ViewModel也会保留。

  1. 假设你的根NavGraph的ID为nav_graph(根据实际项目调整)
  2. 修改SplashScreen的ViewModel获取逻辑:
@Composable
fun SplashScreen(navController: NavController) {
    // 绑定到根NavGraph的BackStackEntry
    val mainViewModel = hiltViewModel<MainViewModel>(navController.getBackStackEntry(R.id.nav_graph))
    
    Splash()
    LaunchedEffect(true) {
        getAllApiCalls(mainViewModel)
        mainViewModel.statusFlow.collectLatest {
            when(it){
                is NetworkResult.Success -> {
                    navController.navigate(Screens.MainScreen.route) {
                        popUpTo(Screens.SplashScreen.route) {
                            inclusive = true
                        }
                    }
                }
                is NetworkResult.Loading -> {...}
                is NetworkResult.Error -> {...}
            }
        }
    }
}
  1. 修改MainScreen的ViewModel获取逻辑:
@Composable
fun MainScreen(navController: NavController) {
    // 同样绑定到根NavGraph的BackStackEntry,获取同一个ViewModel实例
    val mainViewModel = hiltViewModel<MainViewModel>(navController.getBackStackEntry(R.id.nav_graph))
    Main(mainViewModel = mainViewModel)
}

方法二:优化Composable中的协程逻辑(必做)

你当前在Composable重组逻辑中直接启动协程,存在潜在的重复执行问题,建议用LaunchedEffect包裹状态变化后的处理逻辑,去掉@SuppressLint("CoroutineCreationDuringComposition"):

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Main(mainViewModel: MainViewModel) {
    val context = LocalContext.current
    val sheetState = rememberBottomSheetScaffoldState(
        bottomSheetState = SheetState(
            initialValue = SheetValue.Hidden,
            skipPartiallyExpanded = false,
        )
    )
    val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)

    val statusResult by mainViewModel.statusFlow.collectAsState()
    Log.e("TAG",statusResult.toString())

    // 用LaunchedEffect监听statusResult变化,避免重组时重复启动协程
    LaunchedEffect(statusResult) {
        when (statusResult) {
            is NetworkResult.Success -> {
                val statusData = statusResult.data ?: Status()
                mainViewModel.status = statusData
                Log.e("TAG"," hi ${statusData.url}")

                if (statusData.minVersion > VersionHelper.getVersionCode(context)!!) {
                    if (!sheetState.bottomSheetState.isVisible) {
                        drawerState.close()
                        mainViewModel.bottomSheetContents.value = BottomSheetContents.UPDATE
                        sheetState.bottomSheetState.expand()
                    }
                    return@LaunchedEffect
                }

                if (!statusData.functional) {
                    if (!sheetState.bottomSheetState.isVisible) {
                        drawerState.close()
                        mainViewModel.bottomSheetContents.value = BottomSheetContents.UNDER_CONSTRUCTION
                        sheetState.bottomSheetState.expand()
                    }
                    return@LaunchedEffect
                }

                if (!statusData.reject) {
                    if (!sheetState.bottomSheetState.isVisible) {
                        drawerState.close()
                        mainViewModel.bottomSheetContents.value = BottomSheetContents.DISABLED_USER
                        sheetState.bottomSheetState.expand()
                    }
                    return@LaunchedEffect
                }
            }
            is NetworkResult.Error -> {
                Log.e("TAG", "error api statusResult : ${statusResult.message}")
                if (!sheetState.bottomSheetState.isVisible) {
                    drawerState.close()
                    mainViewModel.bottomSheetContents.value = BottomSheetContents.UNDER_CONSTRUCTION
                    sheetState.bottomSheetState.expand()
                }
            }
            is NetworkResult.Loading -> {}
        }
    }

    // ... rest of the code
}

其他可选方案

  • 持久化API结果:如果需要ViewModel销毁后仍保留状态,可以将API结果存储到DataStore或SharedPreferences,在ViewModel初始化时读取并更新statusFlow,适合需要离线状态的场景。
  • 保留SplashScreen的BackStackEntry:去掉popUpTo中的inclusive = true,但这会导致用户返回时回到SplashScreen,不符合常规APP的导航逻辑,不推荐。

内容的提问来源于stack exchange,提问作者farzin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 13:19:52