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

Jetpack Compose跳转新页面后加载Spinner无法正常显示求助

问题描述

作为Compose Navigation新手,我想要实现点击按钮时调用ViewModel中的函数,通过StateFlow触发加载状态,同时导航至新页面并显示加载Spinner。我用delay(5000)想在获取数据前长时间显示Spinner,但实际Spinner在数据加载完成后才出现,求指导。

提供的代码如下:

MainActivityViewModel.kt

class MainActivityViewModel(private val resultRepository: ResultRepository) : ViewModel() {

    val stateResultFetchState = MutableStateFlow<ResultFetchState>(ResultFetchState.OnEmpty)

    fun getSportResult() {
        viewModelScope.launch {
            stateResultFetchState.value = ResultFetchState.IsLoading
            val result = resultRepository.getSportResult()
            delay(5000)
            result.handleResult(
                onSuccess = { response ->
                    if (response != null) {
                        stateResultFetchState.value = ResultFetchState.OnSuccess(response)
                    } else {
                        stateResultFetchState.value = ResultFetchState.OnEmpty
                    }
                },
                onError = {
                    stateResultFetchState.value =
                        ResultFetchState.OnError(it.errorResponse?.errorMessage)
                }
            )
        }
    }
}

SetupMainActivityView.kt

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SetupMainActivityView(
    viewModel: MainActivityViewModel = koinViewModel(),
    navigateToNext: () -> Unit,
) {
    Scaffold(topBar = {
        TopAppBar(
            title = { Text(text = stringResource(id = R.string.app_name)) },
            backgroundColor = getBackgroundColor(),
            elevation = 0.dp
        )
    }, content = { padding ->
        Column(
            modifier = Modifier
                .fillMaxSize()
                .background(getBackgroundColor())
                .padding(padding),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Button(onClick = {
                viewModel.getSportResult()
            }) {
                Text(text = stringResource(id = R.string.get_result))
            }
        }
    })
    when (val state = viewModel.stateResultFetchState.collectAsState().value) {
        is ResultFetchState.OnSuccess -> {}
        is ResultFetchState.IsLoading -> {
            navigateToNext()
        }
        is ResultFetchState.OnError -> {}
        is ResultFetchState.OnEmpty -> {}
    }
}
@Composable
internal fun NavigationGraph() {
    val navController = rememberNavController()
    NavHost(navController = navController, startDestination = ScreenRoute.Home.route) {
        composable(ScreenRoute.Home.route) {
            SetupMainActivityView{
                navController.navigate(ScreenRoute.Result.route)
            }
        }

        composable(ScreenRoute.Result.route) {
            ResultScreen()
        }
    }
}

ResultScreen.kt

@Composable
fun ResultScreen() {
    CircularProgressIndicator()
}
问题分析

核心问题出在协程阻塞与导航时机的耦合:

  1. getSportResult中,设置IsLoading状态后立即执行了同步的resultRepository.getSportResult()(假设该方法是阻塞式的),导致ViewModelScope的协程被阻塞,IsLoading的状态更新无法立即被Compose收集,直到数据加载完成后才会触发导航,此时Spinner自然不会提前显示。
  2. 导航逻辑依赖IsLoading状态,进一步延迟了页面跳转的时机,导致Spinner无法及时展示。
解决方案

1. 异步化数据加载操作

如果resultRepository.getSportResult()是同步阻塞方法,将其切换到IO线程执行,避免阻塞UI协程,确保IsLoading状态能立即发送:

fun getSportResult() {
    viewModelScope.launch {
        stateResultFetchState.value = ResultFetchState.IsLoading
        // 将同步操作切换到IO线程,释放UI协程
        val result = withContext(Dispatchers.IO) {
            resultRepository.getSportResult()
        }
        // 真实场景下移除delay,仅用于模拟时保留
        delay(5000)
        result.handleResult(
            onSuccess = { response ->
                stateResultFetchState.value = if (response != null) {
                    ResultFetchState.OnSuccess(response)
                } else {
                    ResultFetchState.OnEmpty
                }
            },
            onError = {
                stateResultFetchState.value = ResultFetchState.OnError(it.errorResponse?.errorMessage)
            }
        )
    }
}

2. 调整导航与加载的顺序

点击按钮时先导航到ResultScreen,再触发数据加载,确保Spinner能立即显示:
修改SetupMainActivityView中的按钮点击逻辑:

Button(onClick = {
    // 先跳转页面,再触发数据加载
    navigateToNext()
    viewModel.getSportResult()
}) {
    Text(text = stringResource(id = R.string.get_result))
}

同时删除原来在SetupMainActivityView中基于状态触发导航的代码块:

// 移除这段代码
when (val state = viewModel.stateResultFetchState.collectAsState().value) {
    is ResultFetchState.OnSuccess -> {}
    is ResultFetchState.IsLoading -> {
        navigateToNext()
    }
    is ResultFetchState.OnError -> {}
    is ResultFetchState.OnEmpty -> {}
}

3. 让ResultScreen关联ViewModel状态

修改ResultScreen,监听ViewModel的状态变化,动态切换Spinner和内容界面:

@Composable
fun ResultScreen(viewModel: MainActivityViewModel = koinViewModel()) {
    val state = viewModel.stateResultFetchState.collectAsState().value

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        when (state) {
            is ResultFetchState.IsLoading -> CircularProgressIndicator()
            is ResultFetchState.OnSuccess -> {
                // 替换为实际数据展示逻辑
                Text(text = "数据加载完成:${response.toString()}")
            }
            is ResultFetchState.OnError -> Text(text = "加载失败:${state.errorMessage ?: "未知错误"}")
            is ResultFetchState.OnEmpty -> Text(text = "暂无数据")
        }
    }
}

4. 确保StateFlow状态在主线程发送(可选)

如果涉及跨线程操作,确保状态更新在主线程执行,避免Compose收集状态时出现异常:

viewModelScope.launch(Dispatchers.Main) {
    stateResultFetchState.value = ResultFetchState.IsLoading
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 09:18:13