Android基于RxJava实现Paging3分页时列表数据不显示问题
Android RxJava实现Paging3分页PagingData为空问题排查
在Android示例项目中通过RxJava实现Paging3分页功能,远程数据请求成功且已传递到PagingSource,但后续流程未正常推进,调试ViewModel和Activity相关逻辑时发现PagingData为空。
相关实现代码
PagingSource
package com.android.photosearch.data.source.remote import androidx.paging.PagingSource.LoadResult.Page.Companion.COUNT_UNDEFINED import androidx.paging.PagingState import androidx.paging.rxjava2.RxPagingSource import com.android.photosearch.data.source.remote.model.PhotoSearchResponse import com.android.photosearch.domain.model.Photo import com.android.photosearch.util.Constants import io.reactivex.Single import io.reactivex.schedulers.Schedulers class PhotoPagingSource( private val retrofitService: RetrofitService, private var query: String ) : RxPagingSource<Int, Photo>() { override fun loadSingle( params: LoadParams<Int> ): Single<LoadResult<Int, Photo>> { // Start refresh at page 1 if undefined. var nextPageNumber = params.key if (nextPageNumber == null) { nextPageNumber = 1 } return retrofitService.getPhotos(query, Constants.PHOTO_PAGE_SIZE, nextPageNumber) .subscribeOn(Schedulers.io()) .map { response: PhotoSearchResponse -> toLoadResult(response, nextPageNumber) } .onErrorReturn { LoadResult.Error(it) } } private fun toLoadResult( response: PhotoSearchResponse, position:Int ): LoadResult<Int, Photo> { return LoadResult.Page( response.photosGeneralResponse.photoList, null, // Only paging forward. position + 1, COUNT_UNDEFINED, COUNT_UNDEFINED ) } override fun getRefreshKey(state: PagingState<Int, Photo>): Int? { // Try to find the page key of the closest page to anchorPosition, from // either the prevKey or the nextKey, but you need to handle nullability // here: // * prevKey == null -> anchorPage is the first page. // * nextKey == null -> anchorPage is the last page. // * both prevKey and nextKey null -> anchorPage is the initial page, so // just return null. val anchorPosition = state.anchorPosition ?: return null val (_, prevKey, nextKey) = state.closestPageToPosition(anchorPosition) ?: return null if (prevKey != null) { return prevKey + 1 } return if (nextKey != null) { nextKey - 1 } else null } }
Repository实现
package com.android.photosearch.data.repository import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData import androidx.paging.rxjava2.flowable import com.android.photosearch.data.source.remote.PhotoPagingSource import com.android.photosearch.data.source.remote.RetrofitService import com.android.photosearch.domain.model.Photo import com.android.photosearch.domain.repository.PhotoRepository import io.reactivex.Flowable import kotlinx.coroutines.ExperimentalCoroutinesApi import javax.inject.Inject /** * This repository is responsible for * fetching data [photo] from server * */ class PhotoRepositoryImp @Inject constructor( private val retrofitService: RetrofitService ) : PhotoRepository { @ExperimentalCoroutinesApi override fun getPhotos( searchQuery: String, photosPerPage: Int ): Flowable<PagingData<Photo>> { val pagingSource = PhotoPagingSource(retrofitService, searchQuery) return Pager( config = PagingConfig( pageSize = 20, prefetchDistance = 1, enablePlaceholders = false ), pagingSourceFactory = { pagingSource } ).flowable } }
ViewModel核心代码
... fun loadPhotos() : Flowable<PagingData<Photo>> { return repository.getPhotos(searchQuery, Constants.PHOTO_PAGE_SIZE) } ...
UI层核心代码
mDisposable.add(viewModel.loadPhotos().subscribe { pagingData -> searchResultsAdapter?.submitData(lifecycle,pagingData) })
LoadState监听信息
CombinedLoadStates(
refresh=Loading(endOfPaginationReached=false), prepend=NotLoading(endOfPaginationReached=true), append=NotLoading(endOfPaginationReached=false), source=LoadStates(refresh=Loading(endOfPaginationReached=false), prepend=NotLoading(endOfPaginationReached=true), append=NotLoading(endOfPaginationReached=false)),
mediator=null
)
问题根源
适配器实现存在错误:最初编写了普通RecyclerView.Adapter,后续改为继承PagingDataAdapter时忘记删除自定义的getItemCount方法和多余的本地photos列表变量,导致列表无法正常渲染。切换到Paging2后才定位到该问题,Paging3的PagingData调试难度相对更高。
错误适配器代码
package com.android.photosearch.presentation.search import android.view.LayoutInflater import android.view.ViewGroup import androidx.databinding.DataBindingUtil import androidx.databinding.ViewDataBinding import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.android.photosearch.R import com.android.photosearch.databinding.HolderPhotoBinding import com.android.photosearch.domain.model.Photo import com.android.photosearch.presentation.search.SearchResultsAdapter.PhotoViewHolder import java.util.* /** * This class is responsible for converting each data entry [Photo] * into [PhotoViewHolder] that can then be added to the AdapterView. */ class SearchResultsAdapter(diffCallback: DiffUtil.ItemCallback<Photo>) : PagingDataAdapter<Photo, PhotoViewHolder>(diffCallback) { // 该行冗余代码也需要删除 private val photos: MutableList<Photo> = ArrayList() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoViewHolder { val holderPhotoBinding = DataBindingUtil.inflate<ViewDataBinding>( LayoutInflater.from(parent.context), R.layout.holder_photo, parent, false ) return PhotoViewHolder(holderPhotoBinding) } override fun onBindViewHolder(holder: PhotoViewHolder, position: Int) { getItem(position)?.let { holder.onBind(it) } } // 该自定义方法需要删除 override fun getItemCount(): Int { return photos.size } inner class PhotoViewHolder(private val dataBinding: ViewDataBinding) : RecyclerView.ViewHolder(dataBinding.root) { fun onBind(photo: Photo) { val holderPhotoBinding = dataBinding as HolderPhotoBinding val photoViewModel = PhotoViewModel(photo) holderPhotoBinding.photoViewModel = photoViewModel } } }
修复方案
- 删除适配器中冗余的本地photos列表变量:
private val photos: MutableList<Photo> = ArrayList() - 删除自定义重写的getItemCount方法,PagingDataAdapter自身会维护列表计数,不需要额外重写
- 修复后无需改动其他Paging3相关逻辑即可正常渲染分页数据
内容的提问来源于stack exchange,提问作者Victor
相关产品推荐
相关产品推荐

