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也会保留。
- 假设你的根NavGraph的ID为
nav_graph(根据实际项目调整) - 修改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 -> {...} } } } }
- 修改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
相关产品推荐
相关产品推荐

