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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 21:12:02