PageKeyedDataSource loadAfter called continuously
Asked Answered
W

2

10

I have a PageKeyedDataSource that continuously calls the loadAfter, and all the items are added in the Recyclerview multiple times. From the API side a null lastEvaluatedKey means give me the first page which kinda makes sense on why it keeps calling to get the first page but A. Shouldn't it stop if there are no more data to get (aka params.key == null? and B. Shouldn't the COMPARATOR in the Adapter disallow the same items being added multiple times? What am I missing?

PageKeyedDataSource.kt

class ReservationsPageKeyedDataSource(private val retryExecutor: Executor) : PageKeyedDataSource<String, Reservation?>() {

    private var retry: (() -> Any)? = null

    val initialLoad = MutableLiveData<PagingNetworkState>()

    fun retryAllFailed() {
        val prevRetry = retry
        retry = null
        prevRetry?.let {
            retryExecutor.execute {
                it.invoke()
            }
        }
    }

    override fun loadInitial(
        params: LoadInitialParams<String>,
        callback: LoadInitialCallback<String, Reservation?>
    ) {
        val request = Api.reservationsService.getReservations(dateType = RERVATIONS_DATE_TYPE.future, last = null)

        initialLoad.postValue(PagingNetworkState.LOADING)

        // triggered by a refresh, execute in sync
        try {
            val response = request.execute()
            val originalData = response.body()?.result?.reservations
            val data = mutableListOf<Reservation>()
            // some data munipulation
            retry = null
            initialLoad.postValue(PagingNetworkState.LOADED)

            callback.onResult(
                data.toList(),
                null,
                response.body()?.result?.lastEvaluatedKey.toString()
            )
        } catch (ioException: IOException) {
            retry = {
                loadInitial(params, callback)
            }
            val error = PagingNetworkState.error(ioException.message ?: "unknown error")
            initialLoad.postValue(error)
        }
    }

    override fun loadBefore(
        params: LoadParams<String>,
        callback: LoadCallback<String, Reservation?>
    ) {
        // no-op
    }

    override fun loadAfter(
        params: LoadParams<String>,
        callback: LoadCallback<String, Reservation?>
    ) {

        // I tried adding an if statement here to check if the params.key is null or not but that didn't help

        Api.reservationsService.getReservations(dateType = RERVATIONS_DATE_TYPE.future, last = params.key)
            .enqueue(object : Callback<ReservationListResponse> {
                override fun onFailure(call: Call<ReservationListResponse>, t: Throwable) {
                    retry = { loadAfter(params, callback) }
                }

                override fun onResponse(
                    call: Call<ReservationListResponse>,
                    response: Response<ReservationListResponse>
                ) {
                    if (response.isSuccessful) {
                        val data = response.body()?.result?.reservations
                        retry = null
                        callback.onResult(
                            data.orEmpty(),
                            response.body()?.result?.lastEvaluatedKey.toString()
                        )
                    } else {
                        retry = { loadAfter(params, callback) }
                    }
                }
            })
    }
}

The comparator in the PagedListAdapter :

companion object {
        val COMPARATOR = object : DiffUtil.ItemCallback<Reservation>() {
            override fun areContentsTheSame(oldItem: Reservation, newItem: Reservation): Boolean =
                oldItem == newItem

            override fun areItemsTheSame(oldItem: Reservation, newItem: Reservation): Boolean =
                oldItem.id == newItem.id
        }
    }
Wingless answered 6/11, 2019 at 17:19 Comment(0)
F
1

The code looks mostly fine, but it seems strange that you're turning the next token part of the response into a string via toString, and using that as your key.

Instead of PageKeyedDataSource<String, Reservation?>, try PageKeyedDataSource<KeyType, Reservation> (Not sure what the type of that key is from the code above).

Then you can take the next token directly from the API, and pass it into the last parameter of your API without modifying it.

You should also use non-nullable Reservation - the Paging library expects that the items loaded are non-null, since nulls are reserved for representing placeholders: https://developer.android.com/reference/androidx/paging/DataSource#implementing-a-datasource

Fleischer answered 8/11, 2019 at 15:18 Comment(1)
When i have one item also loadAfter call and repeats, Why?Erythrocyte
R
5

Another propable reason is if you use Recycleview in a internal of ScrollView, the loadAfter will be called infinitely untill the data is done. Recycleview will handle the scroll automaticlly, so do not use it in scrollView.

Rambouillet answered 17/8, 2020 at 15:10 Comment(5)
Thanks man, you saved my day! I confirm - it you use RecyclerView inside NestedScrollView, loadAfter() gets called continuously with any scrolling.Meanly
Thanks a lot! This really solved my issue, where I had a RecyclerView inside a NestedScrollView.Eury
I had a RecyclerView insideConstraintLayout inside NestedScrollView. Since I had other UI items defined under <include layout=""/> tag along with RecyclerView in ConstraintLayout, I resolved this issue by adding the UI items other than RecyclerView in a separate adapter and used ConcatAdapter to merge the newly created adapter with the PagedAdapter.Eury
Here is the solution: github.com/kavinraju/IconFinderApp/issues/1Eury
Thanks a lot for this. I tried too many things without knowing this. Thanks!!Jett
F
1

The code looks mostly fine, but it seems strange that you're turning the next token part of the response into a string via toString, and using that as your key.

Instead of PageKeyedDataSource<String, Reservation?>, try PageKeyedDataSource<KeyType, Reservation> (Not sure what the type of that key is from the code above).

Then you can take the next token directly from the API, and pass it into the last parameter of your API without modifying it.

You should also use non-nullable Reservation - the Paging library expects that the items loaded are non-null, since nulls are reserved for representing placeholders: https://developer.android.com/reference/androidx/paging/DataSource#implementing-a-datasource

Fleischer answered 8/11, 2019 at 15:18 Comment(1)
When i have one item also loadAfter call and repeats, Why?Erythrocyte

© 2022 - 2024 — McMap. All rights reserved.