Search code examples
androidkotlinandroid-recyclerviewandroid-jetpackandroid-paging-library

Is there a way to implement a cursor based pagination with Paging Library 3.0 in Android?


I am consuming a rest API that uses cursor based pagination to show some results. I am wondering if I can use Paging Library 3.0 to paginate it. I have been looking through some mediums and docs and can't seem to find a way to implement it. If any of you has come around any solution, I would be so happy to hear from it!

The api response pagination looks like this:

"paging": {
    "previous": false,
    "next": "https://api.acelerala.com/v1/orders/?store_id=4&after=xyz",
    "cursors": {
        "before": false,
        "after": "xyz"
    }
}

Solution

  • Thanks to @Đặng Anh Hào I was able to get on track. As my cursor is a String and not at Int, the Paging Source load function looks like this:

    override suspend fun load(params: LoadParams<String>): LoadResult<String, Order> {
        return try{
            val response = service.getOrders(query,params.key?:"",10)
            val nextKey = if(response.paging?.cursors?.after=="false") null else response.paging?.cursors?.after
            val prevKey = if(response.paging?.cursors?.before=="false") null else response.paging?.cursors?.before
            LoadResult.Page(response.data?.toOrderList()?:emptyList(),prevKey,nextKey)
        }catch (exception: IOException) {
            LoadResult.Error(exception)
        } catch (exception: retrofit2.HttpException) {
            LoadResult.Error(exception)
        }
    }
    

    and the onrefreshkey looks like this:

    override fun getRefreshKey(state: PagingState<String, Order>): String? {
        return state.anchorPosition?.let {
            state.closestItemToPosition(it)?.orderId
        }
    }
    

    The repository method looks like this:

    fun getOrdersPaginated(storeId: String): Flow<PagingData<Order>> {
        return Pager(
            config = PagingConfig(enablePlaceholders = false,pageSize = 10),
            pagingSourceFactory = {PagingSource(apiService,storeId)}
        ).flow
    
    }
    

    And the View Model method is like this:

    private val _pagedOrders = MutableLiveData<PagingData<Order>>()
    val orders get() = _pagedOrders
    
    private var currentQueryValue: String? = null
    private var currentSearchResult: Flow<PagingData<Order>>? = null
    
    fun getOrdersPaginated(storeId: String) {
        viewModelScope.launch {
            currentQueryValue = storeId
            val newResult: Flow<PagingData<Order>> = repository.getOrdersPaginated(storeId)
                .cachedIn(viewModelScope)
            currentSearchResult = newResult
            currentSearchResult!!.collect {
                _pagedOrders.value = it
            }
        }
    }
    

    The activity calls the paging like this:

    private var searchJob: Job? = null
    
    private fun getOrders() {
        viewModel.getOrdersPaginated(storeId)
    }
    
    private fun listenForChanges() {
        viewModel.orders.observe(this, {
            searchJob?.cancel()
            searchJob = lifecycleScope.launch {
                ordersAdapter.submitData(it)
            }
        })
    }
    

    And finally the adapter is the same as a ListAdapter, the only thing that changes is that it now extends PagingDataAdapter<Order, OrderAdapter.ViewHolder>(OrdersDiffer)

    For a more detailed tutorial on how to do it, I read this codelab