Jetpack Compose导航跳转时ListScreen两次触发SUCCESS状态重组原因求解
问题原因
导致ListScreen两次重组的核心原因有两个:
- 你在
NavGraph可组合函数的顶层直接调用了userViewModel.fetchUsers(1),没有用副作用函数包裹。可组合函数的顶层代码会在每次重组时都执行,导航动作会触发NavGraph重组,进而再次触发接口请求。每次请求都会先将UiState置为LOADING、请求成功后置为SUCCESS,两次状态变更会触发ListScreen两次重组(你当前日志没打印第二次请求可能是日志截断或者请求走了缓存没有执行print逻辑)。 - 你传递给ListScreen的
onUserClicked点击回调是在composable代码块内直接创建的未被记忆的Lambda实例,每次重组都会生成新的Lambda对象,ListScreen的入参发生变化,就会触发不必要的重组。
此外你当前的ViewModel创建方式也存在问题:使用remember { mutableStateOf(UsersViewModel()) }创建的ViewModel不会和页面宿主的生命周期绑定,配置变更(如屏幕旋转、切后台)时会丢失数据,也可能引发意外的状态变更。
修复方案
- 把接口请求逻辑放到
LaunchedEffect中,保证仅在NavGraph首次初始化时调用一次,同时修正ViewModel的获取方式:
@Composable private fun NavGraph( navController: NavHostController, scaffoldState: ScaffoldState, paddingValues: PaddingValues ) { // 修正ViewModel获取方式,和宿主生命周期绑定 val userViewModel: UsersViewModel = viewModel() val uiState: UiState<List<User>> by userViewModel.state.collectAsState() // 仅首次进入时请求数据,后续不会重复触发 LaunchedEffect(Unit) { userViewModel.fetchUsers(1) } // 剩余NavHost逻辑不变... }
- 记忆点击回调,避免每次重组生成新实例触发不必要重组:
composable(route = "start_destination") { // 记忆点击回调,仅当navController变化时才重新生成实例 val onUserClicked = remember(navController) { { user: User -> navController.navigate("detail/${user.id}") } } ListScreen(scaffoldState, uiState, onUserClicked) }
内容的提问来源于stack exchange,提问作者Thracian
相关产品推荐
相关产品推荐

