如何在Android Jetpack Compose中实现API分页(MVVM架构)
实现MVVM架构电影应用的分页功能
步骤1:修改API接口,支持动态页码参数
移除硬编码的页码,改为通过@Query动态传入参数:
interface ApiInterface { @GET("movies") suspend fun getMovies( @Query("page") page: Int ): Response<MoviesList> @GET("movies/{movie_id}") suspend fun getDetails( @Path("movie_id") id: Int ): Response<Details> }
步骤2:更新ViewModel,管理分页状态
新增分页相关状态变量,修改数据加载逻辑为追加模式,并根据返回的Metadata判断是否还有更多数据:
class MovieViewModel : ViewModel() { var movieListResponse: List<Data> by mutableStateOf(listOf()) var detailsResponse: Details by mutableStateOf(Details()) private var error: String by mutableStateOf("") var id: Int by mutableIntStateOf(0) // 分页核心状态 private var currentPage by mutableIntStateOf(1) private var isLoading by mutableStateOf(false) private var hasMoreData by mutableStateOf(true) // 初始化/刷新数据(重置页码与列表) fun refreshMovieList() { currentPage = 1 hasMoreData = true movieListResponse = emptyList() loadMoviePage() } // 触发加载下一页 fun loadNextPage() { if (!isLoading && hasMoreData) { currentPage++ loadMoviePage() } } private fun loadMoviePage() { viewModelScope.launch { isLoading = true try { val apiResponse = RetrofitInstance.api.getMovies(currentPage) apiResponse.body()?.let { moviesList -> // 追加新数据到现有列表 movieListResponse = movieListResponse + moviesList.data // 根据Metadata判断是否还有更多页 val metadata = moviesList.metadata hasMoreData = metadata.current_page.toInt() < metadata.page_count } } catch (e: Exception) { error = e.message.toString() // 加载失败回退页码,避免后续请求错误 currentPage-- } finally { isLoading = false } } } fun getDetailsById() { viewModelScope.launch { try { val api = RetrofitInstance.api.getDetails(id = id) detailsResponse = api.body()!! } catch (e: Exception) { error = e.message.toString() } } } }
步骤3:更新UI,实现滚动触发加载
在LazyVerticalGrid中添加滚动监听逻辑,触发下一页加载,并显示加载状态:
@Composable fun HomeScreenUi( moviesList: List<Data>, navController: NavHostController, onLoadNextPage: () -> Unit, isLoading: Boolean, hasMoreData: Boolean ) { LazyVerticalGrid( columns = GridCells.Fixed(2), modifier = Modifier .padding(paddingValues) .fillMaxSize() .background(Color.Transparent), content = { items(moviesList.size) { index -> HomeScreenItemUi( itemIndex = index, movies = moviesList, navController = navController ) // 滚动到倒数第二个item时触发下一页加载 if (index == moviesList.size - 2 && !isLoading && hasMoreData) { onLoadNextPage() } } // 显示加载状态或无更多数据提示 item { when { isLoading -> CircularProgressIndicator( modifier = Modifier.fillMaxWidth().padding(16.dp) ) !hasMoreData -> Text( text = "已加载全部内容", modifier = Modifier.fillMaxWidth().padding(16.dp), textAlign = TextAlign.Center ) } } } ) }
步骤4:关联ViewModel与UI
在宿主Composable中绑定ViewModel状态,传递给UI组件:
@Composable fun HomeScreen(navController: NavHostController) { val viewModel: MovieViewModel = viewModel() // 初始化加载第一页 LaunchedEffect(Unit) { viewModel.refreshMovieList() } HomeScreenUi( moviesList = viewModel.movieListResponse, navController = navController, onLoadNextPage = { viewModel.loadNextPage() }, isLoading = viewModel.isLoading, hasMoreData = viewModel.hasMoreData ) // 显示错误提示(可选) viewModel.error.takeIf { it.isNotEmpty() }?.let { error -> Text( text = error, color = Color.Red, modifier = Modifier.padding(16.dp) ) } }
关键注意事项
- 数据追加逻辑:加载下一页时必须合并新旧数据,避免列表清空刷新
- 状态防重:通过
isLoading防止同一时间发起多个分页请求 - 边界判断:利用返回的
Metadata中的current_page和page_count判断是否还有更多数据 - 错误恢复:加载失败时回退页码,保证后续请求的页码正确性
内容的提问来源于stack exchange,提问作者Mohsen Mashkour
相关产品推荐
相关产品推荐

