Compose + Paging 3:分页加载与无限列表

2024-06-03 · 26 min · 分页加载

Paging 3 是 Jetpack 提供的分页加载库,可以高效地加载和显示大量数据。结合 Compose 的 LazyColumn,可以轻松实现无限滚动列表。

一、实现 PagingSource

class ArticlePagingSource(
    private val apiService: ApiService
) : PagingSource<Int, Article>() {
    
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Article> {
        val page = params.key ?: 1
        
        return try {
            val response = apiService.getArticles(page, params.loadSize)
            LoadResult.Page(
                data = response.articles,
                prevKey = if (page == 1) null else page - 1,
                nextKey = if (response.articles.isEmpty()) null else page + 1
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }
}

二、创建 Pager

val articles: Flow<PagingData<Article>> = Pager(
    config = PagingConfig(
        pageSize = 20,
        prefetchDistance = 5,
        enablePlaceholders = false
    ),
    pagingSourceFactory = { ArticlePagingSource(apiService) }
).flow.cachedIn(viewModelScope)

三、在 Compose 中使用

@Composable
fun ArticleList(viewModel: ArticleViewModel) {
    val articles = viewModel.articles.collectAsLazyPagingItems()
    
    LazyColumn {
        items(
            count = articles.itemCount,
            key = articles.itemKey { it.id }
        ) { index ->
            articles[index]?.let { ArticleCard(it) }
        }
        
        // 底部加载状态
        when (articles.loadState.append) {
            is LoadState.Loading -> {
                item { CircularProgressIndicator() }
            }
            is LoadState.Error -> {
                item { ErrorItem(onRetry = { articles.retry() }) }
            }
            else -> {}
        }
    }
}

四、RemoteMediator 离线优先

@OptIn(ExperimentalPagingApi::class)
class ArticleRemoteMediator(...) : RemoteMediator<Int, ArticleEntity>() {
    
    override suspend fun load(
        loadType: LoadType,
        state: PagingState<Int, ArticleEntity>
    ): MediatorResult {
        // 从网络加载并存入数据库
        database.withTransaction {
            if (loadType == LoadType.REFRESH) {
                articleDao.clearAll()
            }
            articleDao.insertAll(entities)
        }
        return MediatorResult.Success(endOfPaginationReached)
    }
}

五、最佳实践

总结