Updating Paging 3 alpha to stable cause indexing issue Android

Viewed 1204

Hey I am using Paging 3 library with ViewPager 2. In which it loads unlimited data.

implementation "androidx.paging:paging-runtime-ktx:3.0.0-alpha07"

DataSource.kt

package com.example.viewpagerexample

import java.util.*

class DataSource(
    private val size: Int = 5,
    private val currentDate: Date,
    private val limitDate: Date?
) {

    fun returnData(pageNumber: Int): List<Date> {

        val dateList = mutableListOf<Date>()
        val startDateForPage = startDate(pageNumber)
        val tempCalendar = Calendar.getInstance()

        tempCalendar.time = startDateForPage
        val lastDateForPage = endDate(startDateForPage)

        while (tempCalendar.time < lastDateForPage) {
            if (limitDate == null ||
                tempCalendar.time.before(limitDate) ||
                tempCalendar.time == limitDate
            ) {
                dateList.add(tempCalendar.time)
                tempCalendar.add(Calendar.DATE, 1)
            } else {
                break
            }
        }
        return dateList
    }

    private fun startDate(pageNumber: Int): Date {
        if (pageNumber == 0) {
            return currentDate
        } else {
            Calendar.getInstance().let {
                it.time = currentDate
                it.add(Calendar.DATE, pageNumber * size)
                return it.time
            }
        }
    }

    private fun endDate(firstDateForPage: Date): Date {
        Calendar.getInstance().let {
            it.time = firstDateForPage
            it.add(Calendar.DATE, size)
            return it.time
        }
    }
}

ViewPagerPagingSource.kt

class ViewPagerPagingSource(
    private val dataSource: DataSource
) : PagingSource<Int, Date>() {

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Date> {
        val position = params.key ?: 0

        return try {
            val data = dataSource.returnData(position)
            LoadResult.Page(
                data = data,
                prevKey = if (data.isEmpty()) null else position - 1,
                nextKey = if (data.isEmpty()) null else position + 1,
                itemsBefore = LoadResult.Page.COUNT_UNDEFINED,
                itemsAfter = LoadResult.Page.COUNT_UNDEFINED
            )
        } catch (exception: IOException) {
            LoadResult.Error(exception)
        }
    }

}

All code working fine. when application starts it loads current date page.

enter image description here

Now when I am updating the library to this version

implementation "androidx.paging:paging-runtime-ktx:3.0.1"

It jumping to -1 page I guess and look like this

enter image description here

I don't understand why this is causing the issue and also i edited ViewPagerPagingSource to implement another method

override fun getRefreshKey(state: PagingState<Int, Date>): Int? {
        return state.anchorPosition
}

I don't understand what is causing the issue after updating the library. I am adding my github repository Example. Please someone suggest me what is going wrong in code?

UPDATE

I tried to learn the paging concept and update my code. Also, do changes as per @dlam suggestion.

Again is jumping 1 page if I pass few days before date as current date. Github

2021-11-21 21:20:40.820 5460-5460/com.example.viewpagerexample E/Page -1: [06/11/2021, 07/11/2021, 08/11/2021, 09/11/2021, 10/11/2021]
2021-11-21 21:20:40.821 5460-5460/com.example.viewpagerexample E/Page 0: [11/11/2021, 12/11/2021, 13/11/2021, 14/11/2021, 15/11/2021]
2021-11-21 21:20:40.821 5460-5460/com.example.viewpagerexample E/Page 1: [16/11/2021, 17/11/2021, 18/11/2021, 19/11/2021, 20/11/2021]

UPDATE 2

After @MuhannadFakhouri answer I tried this logic

package com.example.viewpagerexample

import java.util.*

class DataSource(
    private val size: Int = 5,
    private val currentDate: Date,
    private val limitDate: Date? = null
) {

    fun returnData(pageNumber: Int): Result {
        val dateList = mutableListOf<Date>()

        val startDateForPage = startDate(pageNumber)
        val tempCalendar = Calendar.getInstance()

        tempCalendar.time = startDateForPage
        val lastDateForPage = endDate(startDateForPage)

        var index = size
        while (tempCalendar.time <= lastDateForPage && index-- > 0) {
            dateList.add(tempCalendar.time)
            tempCalendar.add(Calendar.DATE, 1)
        }
        val limitCalendar = Calendar.getInstance().apply { limitDate }
        return Result(
            result = dateList,
            pageNumber - 1,
            if (dateList.lastOrNull()?.let { Calendar.getInstance().apply { time = it } }
                    ?.let {
                        it.get(Calendar.YEAR) == limitCalendar.get(Calendar.YEAR) &&
                                it.get(Calendar.DAY_OF_YEAR) == limitCalendar.get(Calendar.DAY_OF_YEAR)
                    } == true)
                null
            else
                pageNumber + 1
        )
    }

    private fun startDate(pageNumber: Int): Date {
        Calendar.getInstance().let {
            it.time = currentDate
            it.add(Calendar.DATE, pageNumber * size)
            return it.time
        }
    }

    private fun endDate(firstDateForPage: Date): Date? {
        Calendar.getInstance().let {
            it.time = firstDateForPage
            it.add(Calendar.DATE, size)

            limitDate?.let { limit ->
                return if (it.time > limit) limit else it.time
            } ?: run {
                return it.time
            }
        }
    }

    data class Result(
        val result: MutableList<Date>,
        val prevKey: Int?,
        val nextKey: Int?
    )
}

Logs

2022-01-08 23:39:12.601 7886-7886/com.example.viewpagerexample E/Page -1: [24/12/2021, 25/12/2021, 26/12/2021, 27/12/2021, 28/12/2021]
2022-01-08 23:39:12.603 7886-7886/com.example.viewpagerexample E/Page 0: [29/12/2021, 30/12/2021, 31/12/2021, 01/01/2022, 02/01/2022]
2022-01-08 23:39:12.604 7886-7886/com.example.viewpagerexample E/Page 1: [03/01/2022, 04/01/2022, 05/01/2022, 06/01/2022, 07/01/2022]

ScreenShot

enter image description here

It need to show 29/12/2021 date but it showing me 03/01/2022

NEW EXPLANATION IN DETAILS

Youtube Link with detail explanination

First It opens screens in which only one button. when I click on this it will open viewpager screen. Whenever I click it opening different date screen. My main point is If I pass as current date it will open for current date as view pager main screen. If I pass any date for example 29/12/2021 so it will open this date. The new attached video is showing how it behaving. It will show the code, logs and emulator.

First time when I clicked it open 04/01/22 which is index -1 you can check in video

2022-01-09 00:01:03.108 9637-9637/com.example.viewpagerexample E/Page -1: [04/01/2022, 05/01/2022, 06/01/2022, 07/01/2022, 08/01/2022]
2022-01-09 00:01:03.109 9637-9637/com.example.viewpagerexample E/Page 0: [09/01/2022]
2022-01-09 00:01:03.109 9637-9637/com.example.viewpagerexample E/Page 1: []

Second time it happens same

2022-01-09 00:01:06.488 9637-9637/com.example.viewpagerexample E/Page -1: [04/01/2022, 05/01/2022, 06/01/2022, 07/01/2022, 08/01/2022]
2022-01-09 00:01:06.488 9637-9637/com.example.viewpagerexample E/Page 0: [09/01/2022]
2022-01-09 00:01:06.488 9637-9637/com.example.viewpagerexample E/Page 1: []

Third time It opens 09/01/22 which it correct.

2022-01-09 00:01:09.181 9637-9637/com.example.viewpagerexample E/Page -1: [04/01/2022, 05/01/2022, 06/01/2022, 07/01/2022, 08/01/2022]
2022-01-09 00:01:09.181 9637-9637/com.example.viewpagerexample E/Page 0: [09/01/2022]
2022-01-09 00:01:09.181 9637-9637/com.example.viewpagerexample E/Page 1: []

My question is this why is this causing this kind of issue sometimes open correct index and sometime it will open wrong index.

Thanks

Logs

2022-01-09 14:15:25.501 21246-21246/com.example.viewpagerexample D/Debugging paging: load: null java.lang.Throwable
        at com.example.viewpagerexample.ViewPagerPagingSource.load(ViewPagerPagingSource.kt:16)
        at androidx.paging.PageFetcherSnapshot.doInitialLoad(PageFetcherSnapshot.kt:283)
        at androidx.paging.PageFetcherSnapshot.access$doInitialLoad(PageFetcherSnapshot.kt:54)
        at androidx.paging.PageFetcherSnapshot$pageEventFlow$1.invokeSuspend(PageFetcherSnapshot.kt:163)
        at androidx.paging.PageFetcherSnapshot$pageEventFlow$1.invoke(Unknown Source:8)
        at androidx.paging.PageFetcherSnapshot$pageEventFlow$1.invoke(Unknown Source:4)
        at androidx.paging.CancelableChannelFlowKt$cancelableChannelFlow$1.invokeSuspend(CancelableChannelFlow.kt:30)
        at androidx.paging.CancelableChannelFlowKt$cancelableChannelFlow$1.invoke(Unknown Source:8)
        at androidx.paging.CancelableChannelFlowKt$cancelableChannelFlow$1.invoke(Unknown Source:4)
        at androidx.paging.SimpleChannelFlowKt$simpleChannelFlow$1$1$producer$1$1.invokeSuspend(SimpleChannelFlow.kt:57)
        at androidx.paging.SimpleChannelFlowKt$simpleChannelFlow$1$1$producer$1$1.invoke(Unknown Source:8)
        at androidx.paging.SimpleChannelFlowKt$simpleChannelFlow$1$1$producer$1$1.invoke(Unknown Source:4)
        at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
        at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:264)
        at androidx.paging.SimpleChannelFlowKt$simpleChannelFlow$1$1$producer$1.invokeSuspend(SimpleChannelFlow.kt:52)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69)
        at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:375)
        at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith$default(DispatchedContinuation.kt:278)
        at kotlinx.coroutines.DispatchedCoroutine.afterResume(Builders.common.kt:256)
        at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:102)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7697)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:516)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
2022-01-09 14:15:25.512 21246-21246/com.example.viewpagerexample D/Debugging paging: load: -1 java.lang.Throwable
        at com.example.viewpagerexample.ViewPagerPagingSource.load(ViewPagerPagingSource.kt:16)
        at androidx.paging.PageFetcherSnapshot.doLoad(PageFetcherSnapshot.kt:406)
        at androidx.paging.PageFetcherSnapshot.access$doLoad(PageFetcherSnapshot.kt:54)
        at androidx.paging.PageFetcherSnapshot$collectAsGenerationalViewportHints$$inlined$collect$1.emit(Collect.kt:135)
        at kotlinx.coroutines.flow.FlowKt__ChannelsKt.emitAllImpl$FlowKt__ChannelsKt(Channels.kt:62)
        at kotlinx.coroutines.flow.FlowKt__ChannelsKt.access$emitAllImpl$FlowKt__ChannelsKt(Channels.kt:1)
        at kotlinx.coroutines.flow.FlowKt__ChannelsKt$emitAllImpl$1.invokeSuspend(Unknown Source:14)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69)
        at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:244)
        at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:161)
        at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
        at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
        at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
        at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:328)
        at kotlinx.coroutines.flow.SharedFlowImpl.tryEmit(SharedFlow.kt:368)
        at androidx.paging.HintHandler$HintFlow.setValue(HintHandler.kt:133)
        at androidx.paging.HintHandler$processHint$1.invoke(HintHandler.kt:85)
        at androidx.paging.HintHandler$processHint$1.invoke(HintHandler.kt:79)
        at androidx.paging.HintHandler$State.modify(HintHandler.kt:119)
        at androidx.paging.HintHandler.processHint(HintHandler.kt:79)
        at androidx.paging.PageFetcherSnapshot.accessHint(PageFetcherSnapshot.kt:197)
        at androidx.paging.PageFetcher$PagerUiReceiver.accessHint(PageFetcher.kt:215)
        at androidx.paging.PagingDataDiffer$collectFrom$2$1$1.invokeSuspend(PagingDataDiffer.kt:175)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7697)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:516)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
2022-01-09 14:15:25.519 21246-21246/com.example.viewpagerexample D/Debugging paging: load: -2 java.lang.Throwable
        at com.example.viewpagerexample.ViewPagerPagingSource.load(ViewPagerPagingSource.kt:16)
        at androidx.paging.PageFetcherSnapshot.doLoad(PageFetcherSnapshot.kt:406)
        at androidx.paging.PageFetcherSnapshot.access$doLoad(PageFetcherSnapshot.kt:54)
        at androidx.paging.PageFetcherSnapshot$collectAsGenerationalViewportHints$$inlined$collect$1.emit(Collect.kt:135)
        at kotlinx.coroutines.flow.FlowKt__ChannelsKt.emitAllImpl$FlowKt__ChannelsKt(Channels.kt:62)
        at kotlinx.coroutines.flow.FlowKt__ChannelsKt.access$emitAllImpl$FlowKt__ChannelsKt(Channels.kt:1)
        at kotlinx.coroutines.flow.FlowKt__ChannelsKt$emitAllImpl$1.invokeSuspend(Unknown Source:14)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69)
        at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:244)
        at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:161)
        at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
        at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
        at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
        at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:328)
        at kotlinx.coroutines.flow.SharedFlowImpl.tryEmit(SharedFlow.kt:368)
        at androidx.paging.HintHandler$HintFlow.setValue(HintHandler.kt:133)
        at androidx.paging.HintHandler$processHint$1.invoke(HintHandler.kt:85)
        at androidx.paging.HintHandler$processHint$1.invoke(HintHandler.kt:79)
        at androidx.paging.HintHandler$State.modify(HintHandler.kt:119)
        at androidx.paging.HintHandler.processHint(HintHandler.kt:79)
        at androidx.paging.PageFetcherSnapshot.accessHint(PageFetcherSnapshot.kt:197)
        at androidx.paging.PageFetcher$PagerUiReceiver.accessHint(PageFetcher.kt:215)
        at androidx.paging.PagingDataDiffer.get(PagingDataDiffer.kt:271)
        at androidx.paging.AsyncPagingDataDiffer.getItem(AsyncPagingDataDiffer.kt:214)
        at androidx.paging.PagingDataAdapter.getItem(PagingDataAdapter.kt:231)
        at com.example.viewpagerexample.ViewPagerAdapter.onBindViewHolder(ViewPagerAdapter.kt:11)
        at com.example.viewpagerexample.ViewPagerAdapter.onBindViewHolder(ViewPagerAdapter.kt:8)
        at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7254)
        at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7337)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6194)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6460)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6300)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6296)
        at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2330)
        at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1631)
        at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1591)
        at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:668)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4309)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:4012)
        at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4578)
        at android.view.View.layout(View.java:22085)
        at android.view.ViewGroup.layout(ViewGroup.java:6290)
        at androidx.viewpager2.widget.ViewPager2.onLayout(ViewPager2.java:527)
        at android.view.View.layout(View.java:22085)
        at android.view.ViewGroup.layout(ViewGroup.java:6290)
        at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1873
2 Answers

I noticed you filed a bug here: https://issuetracker.google.com/204328119, but I also wanted to update this answer for other future people reading this issue on SO.

The core issue is that you are starting collection on Paging before ViewPager is ready to start binding this items. You should use lifecycleScope.launchWhenCreated instead of lifecycleScope.launc to fix this:

lifecycleScope.launchWhenCreated {
  viewModel.dataList.collectLatest {
    adapter.submitData(it)
  }
}

The other issue I noticed (which would also workaround this), is that you have placeholders enabled, but aren't passing in itemsBefore or itemsAfter in LoadResult.Page. Enabling placeholders and having a static count would also give your view a list size to bind, but since you pass COUNT_UNDEFINED, Paging is not able to pad the list with null placeholders properly since it has no idea how many to add.

I didn't look in-deep into this and how this can be related to library's version, but i noticed a glitch in your Datasource logic you're doing the following check

                if (lastDateForPage == limitDate)
                    null
                else
                    pageNumber + 1

after this loop

        while (tempCalendar.time <= lastDateForPage && index-- > 0) {
            dateList.add(tempCalendar.time)
            tempCalendar.add(Calendar.DATE, 1)
        }
        

the loop might actually break without the limit getting reached, this means your datasource doesn't guarantee that limitDate is always reached.

Changing the condition lastDateForPage == limitDate to check on the following, should fix the issue

            val limitCalendar = Calendar.getInstance().apply { limitDate }

            if (dateList.lastOrNull()?.let { Calendar.getInstance().apply { time = it } }
                    ?.let {
                        it.get(Calendar.YEAR) == limitCalendar.get(Calendar.YEAR) &&
                                it.get(Calendar.DAY_OF_YEAR) == limitCalendar.get(Calendar.DAY_OF_YEAR)
                    } == true)
                null
            else
                pageNumber + 1
Related