使用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
相关产品推荐
相关产品推荐

