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

如何在Jetpack Compose中ViewModel加载完成后触发导航

解决Jetpack Compose加载屏导航与ViewModel状态管理问题

问题根源

  1. 导航时机错误:LoadingScreen中直接在Composable函数体调用onNavigateToMainScreen(),导致加载屏刚渲染就触发导航,既看不到进度条,还可能因导航时机违规引发崩溃。
  2. ViewModel状态缺失:原ViewModel没有暴露加载状态,无法监听数据加载完成的节点。
  3. ViewModel创建方式错误:用remember { mutableStateOf(LoadingScreenViewModel()) }创建ViewModel,会在Compose重组时重复实例化,丢失状态。

解决方案步骤

1. 重构ViewModel,添加状态管理

给ViewModel添加StateFlow暴露加载状态,同时实现模拟/真实的数据加载逻辑:

class LoadingScreenViewModel: ViewModel() {
    // 定义加载状态密封类
    sealed class LoadingState {
        object Loading : LoadingState()
        data class Success(val data: String) : LoadingState() // 替换String为你的实际数据类型
        object Error : LoadingState()
    }

    // 用StateFlow对外暴露状态,初始为加载中
    private val _loadingState = MutableStateFlow(LoadingState.Loading)
    val loadingState: StateFlow<LoadingState> = _loadingState

    init {
        loadData()
    }

    private fun loadData() = viewModelScope.launch {
        try {
            // 替换为实际API请求逻辑
            delay(1500)
            // 模拟获取到数据
            val apiData = "从接口获取的内容"
            _loadingState.value = LoadingState.Success(apiData)
        } catch (e: Exception) {
            // 处理加载失败
            _loadingState.value = LoadingState.Error
        }
    }

    // 提供重试方法(可选)
    fun retryLoad() {
        _loadingState.value = LoadingState.Loading
        loadData()
    }
}

2. 修复LoadingScreen的状态监听与导航逻辑

使用viewModel()获取正确的ViewModel实例,通过collectAsStateWithLifecycle监听状态变化,在数据加载完成后触发导航:

@Composable
fun LoadingScreen(onNavigateToMainScreen: (String) -> Unit) {
    val viewModel: LoadingScreenViewModel = viewModel()
    val loadingState by viewModel.loadingState.collectAsStateWithLifecycle()

    MyTheme {
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            // 显示加载进度条
            if (loadingState is LoadingScreenViewModel.LoadingState.Loading) {
                CircularProgressIndicator()
            }

            // 加载失败时显示重试按钮
            if (loadingState is LoadingScreenViewModel.LoadingState.Error) {
                Text(
                    text = "加载失败,点击重试",
                    modifier = Modifier.clickable { viewModel.retryLoad() },
                    color = Color.Red
                )
            }
        }
    }

    // 用LaunchedEffect监听状态变化,仅在状态变为Success时导航
    LaunchedEffect(loadingState) {
        if (loadingState is LoadingScreenViewModel.LoadingState.Success) {
            val data = (loadingState as LoadingScreenViewModel.LoadingState.Success).data
            onNavigateToMainScreen(data)
        }
    }
}

3. 修改NavHost支持数据传递

在导航时传递加载完成的数据,主屏幕通过导航参数接收:

@Composable
fun MyNavHost(
    navController: NavHostController = rememberNavController(),
) {
    NavHost(navController = navController, startDestination = "loadingScreen") {
        composable("loadingScreen") {
            LoadingScreen(
                onNavigateToMainScreen = { data ->
                    navController.navigate("mainScreen/$data")
                }
            )
        }
        composable(
            route = "mainScreen/{data}",
            arguments = listOf(navArgument("data") { type = NavType.StringType })
        ) { backStackEntry ->
            val data = backStackEntry.arguments?.getString("data") ?: "默认数据"
            MainScreen(data = data)
        }
    }
}

4. 主屏幕接收并使用数据

修改MainScreen参数,接收加载完成的数据:

@Composable
fun MainScreen(data: String) {
    MyTheme {
        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
            Text(text = "主屏幕已加载,数据:$data")
        }
    }
}

关键注意点

  • 用collectAsStateWithLifecycle替代直接收集StateFlow,确保状态收集符合组件生命周期,避免内存泄漏。
  • 导航逻辑必须放在LaunchedEffect中,防止Compose重组时重复触发导航。
  • 使用viewModel()获取ViewModel实例,保证ViewModel与屏幕生命周期绑定,不会在重组时重复创建。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 05:10:29