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

使用Navigation Compose后,ViewModel的getExercises()无法通过Retrofit加载JSON数据?

问题分析与解决方案

核心问题:异步请求的同步返回逻辑错误

你的ExerciseViewModel.getExercises()方法里,用viewModelScope.launch发起了异步网络请求,但立刻返回了初始的空exerciseListResponse。网络请求是异步的,此时数据还未获取到,所以UI拿到的自然是空列表。而且后续请求完成更新exerciseListResponse时,UI也没有监听这个状态变化,不会触发重组更新数据。

分步修复

1. 调整ViewModel,让状态可被观察

把getExercises()改成只触发请求、不返回数据,让UI直接观察exerciseListResponse状态变量:

class ExerciseViewModel : ViewModel() {
    private val repository = ExerciseRepositoryImpl()

    var exerciseListResponse: List<Exercise> by mutableStateOf(emptyList())
    var errorMessage: String by mutableStateOf("")

    // 仅负责触发请求,不返回数据
    fun fetchExercises() {
        viewModelScope.launch {
            try {
                val exerciseList = repository.getExercises()
                exerciseListResponse = exerciseList
            } catch (e: Exception) {
                errorMessage = e.message.toString()
                // 打印日志排查具体错误
                Log.e("ExerciseViewModel", "请求失败: ${e.message}")
            }
        }
    }
}

2. 正确获取ViewModel实例

在Composable中不要直接new ExerciseViewModel(),应该用Jetpack提供的viewModel()函数,确保ViewModel绑定到正确的生命周期,避免重复创建:
修改MainActivity的Composable部分:

setContent {
    GymEncV2Theme {
        navController = rememberNavController()
        // 用viewModel()获取实例,而非直接new
        val viewModel: ExerciseViewModel = viewModel()

        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.colors.background
        ) {
            SetupNavGraph(navController = navController, viewModel = viewModel)
        }
    }
}

3. 在UI中观察状态并触发请求

在SampleExerciseScreen中,先触发请求,再观察exerciseListResponse,状态变化时自动重组UI:

@Composable
fun SampleExerciseScreen(navController: NavController, muscleGroup: String, viewModel: ExerciseViewModel) {
    // 首次进入时触发请求(LaunchedEffect确保只执行一次)
    LaunchedEffect(Unit) {
        viewModel.fetchExercises()
    }

    // 观察viewModel状态,状态变化自动重组
    val exerciseList = viewModel.exerciseListResponse.filter { it.muscle == muscleGroup }
    val errorMessage = viewModel.errorMessage

    Surface(modifier = Modifier.fillMaxSize(), color = AppColors.mBackground) {
        Column {
            MyTopBar(navController = navController)

            Text(
                text = muscleGroup,
                style = MaterialTheme.typography.h3,
                color = AppColors.mDetails,
                fontWeight = FontWeight.Bold,
                textAlign = TextAlign.Center
            )

            // 添加错误提示
            if (errorMessage.isNotEmpty()) {
                Text(text = "加载失败: $errorMessage", color = Color.Red)
            }

            UserExercisesButton()
            // 此处可根据exerciseList渲染具体内容
        }
    }
}

4. 额外排查点

  • 检查Constants.BASE_URL是否正确,确保末尾带有/(例如https://example.com/api/而非https://example.com/api)
  • 确认GymEnc_v2.JSON的返回格式与Exercise数据类完全匹配,Gson解析失败会返回空列表或抛出异常,可通过ViewModel的catch块日志查看具体错误
  • 确认已添加网络权限:在AndroidManifest.xml中添加<uses-permission android:name="android.permission.INTERNET" />
  • 若使用Android 9及以上版本,需避免明文网络被拦截:要么使用HTTPS,要么在<application>标签中添加android:usesCleartextTraffic="true"

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 15:50:39