I am currently working on implementing paging functionality using Paging3, incorporating user-input filtering for item titles. The implementation is generally successful, except for a peculiar scenario:
When the combination of the Flow<PagingData> and the Filter flow occurs, if the first page in the PagingData is filtered out (resulting in 0 elements remaining), the PagingDataAdapter does not request any additional pages. However, if you have already scrolled through several pages and this particular element is in the cache (via .cachedIn(viewModelScope)), the functionality works perfectly.
I am uncertain whether passing an empty PagingData in the initial flow combine misleads the adapter into assuming that endOfPagination has been reached or this is the normal functionality.
These are my paging classes:
PagingSource
class SchoolPagingSource (
private var schoolDataBase: SchoolDataBase
) : PagingSource<Int,School>() {
companion object{
const val ITEMS_PER_PAGE = 20
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, School> {
val nextPageNumber = params.key ?: 1
val nextElementPosition = (nextPageNumber - 1) * ITEMS_PER_PAGE
return try {
var response = schoolDataBase.schoolDao().getSchools(ITEMS_PER_PAGE,nextElementPosition)
val key = if (response.size == ITEMS_PER_PAGE)
nextPageNumber + 1
else
null
LoadResult.Page(response, null, key)
} catch (e: Exception) {
e.printStackTrace()
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Int, School>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
}
ViewModel
class SchoolsViewModel @Inject constructor(
private val dataBase: SchoolDataBase,
) : BaseViewModel() {
private val _filterStringState = MutableStateFlow("")
//TODO ARREGLAR
fun getAllSchools(): Flow<PagingData<School>> =
Pager(config =
PagingConfig(
pageSize = ITEMS_PER_PAGE,
prefetchDistance = 20,
enablePlaceholders = true
)) {
SchoolPagingSource(dataBase)
}.flow.cachedIn(viewModelScope).combine(_filterStringState) { pagingData, string ->
pagingData.filter {
it.getName()
.contains(
string
)
}
}.cachedIn(viewModelScope)
fun filterByString(string: String) = launch { _filterStringState.emit(string) }
}
PagingDataAdapter
class SchoolsPagingDataAdapter() :
PagingDataAdapter<School, SchoolsPagingDataAdapter.ViewHolder>(SCHOOL_COMPARATOR) {
companion object{
private val SCHOOL_COMPARATOR = object : DiffUtil.ItemCallback<School>(){
override fun areItemsTheSame(oldItem: School, newItem: School): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: School, newItem: School): Boolean =
oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup,viewType: Int): ViewHolder =
ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_colegio, parent, false)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(getItem(position))
inner class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {}
I kinda fixed it using adapter.refresh() but is has its own problems
Fragment
schoolFragment.setOnSearchTextWatcher {
schoolViewModel.filterByString(it)
if (adapterSchools.snapshot().isEmpty() && !endOfPaginationReached) {
adapterSchools.refresh()
endOfPaginationReached = true
}
}
adapterSchools.addLoadStateListener { loadState ->
if (loadState.refresh is LoadState.NotLoading && loadState.append.endOfPaginationReached)
endOfPaginationReached = true
}

