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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 20:38:20