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

2

4

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"
    }
}
Sponson answered 7/4, 2021 at 3:16 Comment(0)
S
2

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

Sponson answered 11/4, 2021 at 4:5 Comment(0)
B
0

In kotlin, here example.

In Activity or somewhere:

viewModel.triggerGetMoreData("data").collectLatest {
                mAdapter.submitData(it)
            }

In viewModel:

fun triggerGetMoreData(data: String): Flow<PagingData<SampleData>> {
    val request = ExampleRequest(data)
    return exampleRepository.getMoreData(request).cachedIn(viewModelScope)
}

In Repository:

fun getMoreData(request: ExampleRequest): Flow<PagingData<ExampleData>> {
    return Pager(
        config = PagingConfig(
            pageSize = 30,
            enablePlaceholders = false
        ),
        pagingSourceFactory = { ExamplePagingSource(service, request) }
    ).flow
}

and

class ExamplePagingSource (
private val service: ExampleService,
private val request: ExampleRequest): PagingSource<Int, ExampleData>() {

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, ExampleData> {
    return try {
        val pageIndex = params.key ?: 0
        val request = request.copy(index = (request.pageNum.toInt() * pageIndex).toString())
        when (val result = service.getMoreData(request)) { // call api
            is NetworkResponse.Success -> {
                val listData = result.body.items?.toData()?: listOf()
                LoadResult.Page(
                    data = listData,
                    prevKey = if (pageIndex == 0) null else pageIndex - 1,
                    nextKey = if (listData.isEmpty()) null else pageIndex + 1
                )
            }
            else -> LoadResult.Error(result.toError())
        }
    } catch (e: Exception) {
        e.printStackTrace()
        LoadResult.Error(e)
    }
}

}

Boiled answered 7/4, 2021 at 4:9 Comment(4)
Is this approach suitable for cursor based pagination? The API gives me a response with a pagination object that has the before and after URLs to be called for the next or previous page. Would it work with that too?Sponson
please give me example apiHummel
Check ExamplePagingSource class: After receive data, you need modify request to call next api. And change condition check prevKey, nextKey appropriate to your contextHummel
I have updated the question with the api response that has the paging, maybe that gives you a more clear idea of what I am trying to achieveSponson

© 2022 - 2024 — McMap. All rights reserved.