Android Kotlin中如何从单个PagingSource获取两类分页列表数据
问题根因
你当前的实现存在3个核心错误,导致分页逻辑无法正常运行:
- Kotlin中函数return后的代码属于不可达代码,你在
load()方法里写了两个LoadResult.Page,第二个永远不会执行,单个PagingSource单次加载只能返回一个结果 - 初始化的两个
Pager都传入了无类型区分的MoviePagingSource,没有指定要拉取正在热映还是热门电影,两个分页流的请求逻辑完全混乱 - Compose层同时收集两个分页流的写法会造成不必要的资源消耗,哪怕只展示其中一个列表,两个分页请求都会同时触发,浪费流量和内存
正确实现方案
1. 改造通用PagingSource,支持区分列表类型
首先定义枚举标识电影列表类型,让PagingSource根据传入的类型调用对应接口,同时补全Paging3必需的getRefreshKey方法,保证刷新、分页恢复逻辑正常:
// 列表类型枚举 enum class MovieListType { NOW_PLAYING, POPULAR } class MoviePagingSource( private val movieRepository: MovieRepository, private val listType: MovieListType ) : PagingSource<Int, Movie>() { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movie> { return try { val currentPage = params.key ?: 1 // 根据传入的列表类型调用对应接口 val response = when (listType) { MovieListType.NOW_PLAYING -> movieRepository.getNowPlayingMovies(currentPage) MovieListType.POPULAR -> movieRepository.getPopularMovies(currentPage) } val isLastPage = response.page >= response.total_pages LoadResult.Page( data = response.results, prevKey = if (currentPage == 1) null else currentPage - 1, nextKey = if (isLastPage) null else currentPage + 1 ) } catch (e: Exception) { LoadResult.Error(e) } } override fun getRefreshKey(state: PagingState<Int, Movie>): Int? { // 刷新时返回最近访问页面的相邻页,保证分页位置不丢失 return state.anchorPosition?.let { anchorPos -> state.closestPageToPosition(anchorPos)?.prevKey?.plus(1) ?: state.closestPageToPosition(anchorPos)?.nextKey?.minus(1) } } }
2. 优化ViewModel层,用流切换实现单数据源
不要同时创建两个固定的Pager实例,通过flatMapLatest监听当前选中的列表类型,自动切换对应分页流,切换时会自动取消上一个类型的请求,避免资源浪费:
@HiltViewModel class MainViewModel @Inject constructor( private val movieRepository: MovieRepository, private val favMovieRepository: FavMovieRepository ) : ViewModel() { // 用StateFlow持有当前选中的列表类型,默认展示正在热映 private val _currentListType = MutableStateFlow(MovieListType.NOW_PLAYING) val currentListType = _currentListType.asStateFlow() // 分页数据流,类型切换时自动替换数据源 val moviePagingFlow: Flow<PagingData<Movie>> = _currentListType .flatMapLatest { listType -> Pager( config = PagingConfig( pageSize = 10, enablePlaceholders = false, initialLoadSize = 10 ) ) { MoviePagingSource(movieRepository, listType) }.flow } .cachedIn(viewModelScope) // 缓存分页数据,转屏等配置变更时不需要重新加载 // 对外暴露切换列表的方法 fun switchListType(type: MovieListType) { _currentListType.value = type } }
3. 简化Compose层实现
不需要同时收集两个分页流,只需要收集单个分页流即可,切换类型时Paging组件会自动处理数据更新、状态保留:
@Composable fun MovieListView(viewModel: MainViewModel) { val currentListType by viewModel.currentListType.collectAsStateWithLifecycle() val movieItems = viewModel.moviePagingFlow.collectAsLazyPagingItems() LazyColumn { item { Row { // 下拉选择控件 PopularityDropDown( currentSelected = currentListType, onTypeSelected = { selectedType -> viewModel.switchListType(selectedType) } ) } } items(movieItems) { movie -> movie?.let { MovieCard(movie = it) { // 你的收藏判断逻辑 val isFav = favMovieRepository.isFav(it.id) if (isFav) { // 已收藏状态逻辑 } else { Log.d(TAG, "MovieListView: ${it.original_title} with id ${it.id} It is Not favorite ") } } } } // 统一处理加载、错误状态 item { when (movieItems.loadState.append) { is LoadState.Loading -> CircularProgressIndicator( modifier = Modifier.fillMaxWidth().padding(16.dp) ) is LoadState.Error -> { Button( onClick = { movieItems.retry() }, modifier = Modifier.fillMaxWidth().padding(16.dp) ) { Text("加载失败,点击重试") } } else -> {} } } } }
额外优化建议
- 如果需要切换列表时同时保留两个列表的滚动位置和已加载数据,可以在ViewModel中通过
cachedIn分别缓存两个类型的PagingData,切换时直接取对应缓存即可,不需要每次重建PagingSource - PagingConfig可以根据业务调整
prefetchDistance参数,控制列表滑到距离底部多少条时触发下一页预加载,默认值等于pageSize - 不要在PagingSource中持有Context、ViewModel等生命周期长于PagingSource的对象,避免内存泄漏
内容的提问来源于stack exchange,提问作者Ashish Gautam
相关产品推荐
相关产品推荐

