将 Paging 3 alpha 更新为稳定导致索引问题 Android
Posted
技术标签:
【中文标题】将 Paging 3 alpha 更新为稳定导致索引问题 Android【英文标题】:Updating Paging 3 alpha to stable cause indexing issue Android 【发布时间】:2021-12-13 17:19:49 【问题描述】:嘿,我正在使用 Paging 3 库和 ViewPager 2。它在其中加载无限数据。
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)
所有代码工作正常。当应用程序启动时,它会加载当前日期页面。
现在当我将库更新到这个版本时
implementation "androidx.paging:paging-runtime-ktx:3.0.1"
我猜它跳转到 -1 页面,看起来像这样
我不明白为什么这会导致问题,并且我编辑了 ViewPagerPagingSource 以实现另一种方法
override fun getRefreshKey(state: PagingState<Int, Date>): Int?
return state.anchorPosition
我不明白更新库后导致问题的原因。我正在添加我的 github 存储库Example。请有人建议我代码中出了什么问题?
更新
我尝试学习分页概念并更新我的代码。另外,请按照@dlam 的建议进行更改。
如果我将日期前几天作为当前日期传递,则再次跳转 1 页。 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]
更新 2
@MuhannadFakhouri 回答后我尝试了这个逻辑
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?
)
日志
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]
截图
它需要显示 29/12/2021 日期,但它显示了 03/01/2022
新的详细解释
Youtube Link with detail explanination
首先它打开只有一个按钮的屏幕。当我点击它时,它将打开 viewpager 屏幕。每当我点击它打开不同的日期屏幕。我的主要观点是,如果我作为 当前日期 传递,它将在 当前日期 作为查看寻呼机主屏幕打开。如果我通过任何日期,例如 29/12/2021,它将打开这个日期。新附加的视频显示了它的行为方式。它将显示代码、日志和模拟器。
第一次当我点击它打开 04/01/22 这是索引 -1 你可以检查视频
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: []
第二次还是一样
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: []
第三次打开 09/01/22 正确。
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: []
我的问题是为什么这会导致这种问题有时打开正确的 index 有时会打开错误的 index。
谢谢
日志
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
【问题讨论】:
我不太了解 Paging 以查看您是否做错了什么,请在 Google 问题跟踪器上尝试 creating an issue。现在您可以使用适合您的版本。 【参考方案1】:我注意到你在这里提交了一个错误:https://issuetracker.google.com/204328119,但我也想更新这个答案,以供其他未来在 SO 上阅读此问题的人使用。
核心问题是在 ViewPager 准备好开始绑定这些项目之前,您正在 Paging 上开始收集。你应该使用lifecycleScope.launchWhenCreated
而不是lifecycleScope.launc
来解决这个问题:
lifecycleScope.launchWhenCreated
viewModel.dataList.collectLatest
adapter.submitData(it)
我注意到的另一个问题(也可以解决此问题)是您启用了占位符,但没有在LoadResult.Page
中传递itemsBefore
或itemsAfter
。启用占位符并具有静态计数也会为您的视图提供要绑定的列表大小,但是由于您传递了COUNT_UNDEFINED
,因此 Paging 无法使用 null
占位符正确填充列表,因为它不知道要添加多少。
【讨论】:
我可以在itemBefore
或itemAfrer
中传递什么值?
您只需在LoadResult.Page
的构造函数中传递该页面前后的Int
项目数。例如,如果您有 [null, null, 3, null],您将在 Page
中为 itemsBefore
传递 2 和为 itemsAfter
传递 1,其 data
仅包含 3
。
我尝试学习分页逻辑并尝试更新我的逻辑,但仍然导致同样的问题Github。
在我的ViewPagerViewModel
中,我创建了两个函数currentDate
,其中我在当前日期前几天通过。它再次导致从索引 1 开始的相同问题。但如果我通过当前日期,它在limitDate
中工作正常。你知道为什么会导致这个问题吗?【参考方案2】:
我没有深入研究这一点以及这与库的版本有何关系,但我注意到您的数据源逻辑中存在故障 您正在执行以下检查
if (lastDateForPage == limitDate)
null
else
pageNumber + 1
在这个循环之后
while (tempCalendar.time <= lastDateForPage && index-- > 0)
dateList.add(tempCalendar.time)
tempCalendar.add(Calendar.DATE, 1)
循环实际上可能会在没有达到限制的情况下中断,这意味着您的数据源不能保证总是达到 limitDate。
更改条件lastDateForPage == limitDate
以检查以下内容,应该可以解决问题
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
【讨论】:
我试过你的代码它没有工作它给出错误的日期:( 那我需要你描述一下你想要实现的用例,这样我才能理解你面临的问题 我需要在当前页面浏览器中显示为日期。如果我将日期传递为29/11/2021
,则需要以该日期开始屏幕。但现在它采用了错误的索引。
我在详细信息部分添加了新的解释,带有 YouTube 链接,请查看。如果有任何疑问,请告诉我。谢谢
我无法重现该问题,无论如何让我们假设您的数据源没有问题,将此日志语句 Log.d("Debugging paging", "load: $params.key $Throwable().stackTraceToString()")
添加到您的 ViewPagerPagingSource.load
并发布其输出以上是关于将 Paging 3 alpha 更新为稳定导致索引问题 Android的主要内容,如果未能解决你的问题,请参考以下文章
在 Paging 3 库 Android Kotlin 中更新当前页面或更新数据