android - 分页 3 - IllegalArgumentException:前置状态完成后的附加前置事件
问题描述
在使用 Paging 3(基于此 Google GitHub codelab)制作了一个简单的应用程序后,我的应用程序崩溃了。当我向下滚动时,在某个时候(可能是在调用新的 GET 函数时)。Logcat 看起来像这样:
D/NewsRemoteMediator: APPEND
I/okhttp.OkHttpClient: --> GET https://webit-news-search.p.rapidapi.com/search?language=en&number=5&offset=10&q=SearchText
I/okhttp.OkHttpClient: <-- 200 OK https://webit-news-search.p.rapidapi.com/search?language=en&number=5&offset=10&q=SearchText(182ms, 2853-byte body)
2020-11-16 16:39:30.991 10559-10559/com.example.testapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.testapp, PID: 10559
java.lang.IllegalArgumentException: Additional prepend event after prepend state is done
at androidx.paging.SeparatorState.onInsert(Separators.kt:221)
at androidx.paging.SeparatorState.onEvent(Separators.kt:173)
at androidx.paging.SeparatorsKt$insertEventSeparators$$inlined$map$1$2.emit(Collect.kt:135)
at androidx.paging.PagingDataKt$map$$inlined$transform$1$2.emit(Collect.kt:136)
at kotlinx.coroutines.flow.FlowKt__ChannelsKt.emitAllImpl$FlowKt__ChannelsKt(Channels.kt:61)
at kotlinx.coroutines.flow.FlowKt__ChannelsKt$emitAllImpl$1.invokeSuspend(Unknown Source:11)
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:236)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:161)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:362)
at kotlinx.coroutines.CancellableContinuationImpl.completeResume(CancellableContinuationImpl.kt:479)
at kotlinx.coroutines.channels.AbstractChannel$ReceiveElement.completeResumeReceive(AbstractChannel.kt:899)
at kotlinx.coroutines.channels.ArrayChannel.offerInternal(ArrayChannel.kt:84)
at kotlinx.coroutines.channels.AbstractSendChannel.send(AbstractChannel.kt:135)
at androidx.paging.PageFetcherSnapshot.doLoad(PageFetcherSnapshot.kt:487)
at androidx.paging.PageFetcherSnapshot$doLoad$1.invokeSuspend(Unknown Source:12)
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:900)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:219)
at android.app.ActivityThread.main(ActivityThread.java:8347)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055)
2020-11-16 16:39:31.036 10559-10559/com.example.testapp I/Process: Sending signal. PID: 10559 SIG: 9
同样的错误发生在来自 GitHub 的 Google 示例应用程序中,但在他们的案例中,它发生在向下滚动然后向上滚动之后。是否有可能查看导致此错误的确切位置和原因?我试图阅读这个 logcat 错误,但它对我没有任何意义。下面是 RemoteMediator 和 Repository 类,如果有帮助,我可以添加其他类。
@OptIn(ExperimentalPagingApi::class)
class NewsRemoteMediator @Inject constructor(
private val newsRetrofit: NewsRetrofit,
private val database: AppDatabase,
private val networkToEntityMapper: NetworkToEntityMapper
) : RemoteMediator<Int, News>()
{
var searchKey: String? = null
override suspend fun load(loadType: LoadType, state: PagingState<Int, News>): MediatorResult
{
val page: Int = when (loadType)
{
LoadType.REFRESH ->
{
Timber.d("REFRESH")
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: NEWS_STARTING_PAGE_INDEX
}
LoadType.PREPEND ->
{
Timber.d("PREPEND")
val remoteKeys = getRemoteKeyForFirstItem(state)
?: throw InvalidObjectException("Remote key and the prevKey should not be null")
remoteKeys.prevKey
?: return MediatorResult.Success(endOfPaginationReached = true)
remoteKeys.prevKey
}
LoadType.APPEND ->
{
Timber.d("APPEND")
val remoteKeys = getRemoteKeyForLastItem(state)
if (remoteKeys?.nextKey == null)
{
throw InvalidObjectException("Remote key should not be null for $loadType")
}
remoteKeys.nextKey
}
}
try
{
val apiResponse =
if (searchKey.isNullOrEmpty())
newsRetrofit.getTrending(
state.config.pageSize,
page * state.config.pageSize
)
else
newsRetrofit.getSearched(
state.config.pageSize,
page * state.config.pageSize,
searchKey!!
)
val news = apiResponse.data.results
val endOfPaginationReached = news.isEmpty()
database.withTransaction {
// clear all tables in the database
if (loadType == LoadType.REFRESH)
{
Timber.d("Clear db")
database.remoteKeysDao.clearRemoteKeys()
database.newsDao.clearNews()
}
val prevKey = if (page == NEWS_STARTING_PAGE_INDEX) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = news.map {
RemoteKeys(
newsId = it.url,
prevKey = prevKey,
nextKey = nextKey
)
}
database.remoteKeysDao.insertAll(keys)
database.newsDao.insertAll(networkToEntityMapper.mapToNewModelList(news))
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
}
catch (exception: IOException)
{
return MediatorResult.Error(exception)
}
catch (exception: HttpException)
{
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, News>): RemoteKeys?
{
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { news ->
database.remoteKeysDao.remoteKeysRepoId(news.url)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, News>): RemoteKeys?
{
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { news ->
database.remoteKeysDao.remoteKeysRepoId(news.url)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, News>
): RemoteKeys?
{
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.url?.let { newsUrl ->
database.remoteKeysDao.remoteKeysRepoId(newsUrl)
}
}
}
}
存储库类:
@Singleton
class NewsRepository @Inject constructor(
private val database: AppDatabase,
private val newsRemoteMediator: NewsRemoteMediator
)
{
fun getSearchResultStream(searchKey: String?): Flow<PagingData<News>>
{
val pagingSourceFactory = { database.newsDao.getNews() }
return Pager(
config = PagingConfig(
pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false,
initialLoadSize = INITIAL_LOAD_SIZE
),
remoteMediator = newsRemoteMediator.apply { this.searchKey = searchKey },
pagingSourceFactory = pagingSourceFactory
).flow
}
}
解决方案
我不确定,但我认为这只是图书馆中的一个错误。我正在使用3.0.0-alpha08
,但更新到3.0.0-alpha09后我没有收到任何错误。更新版本后,我在我的应用程序和 Google 示例项目中对其进行了测试,它正在运行。
修复了
IllegalArgumentException
在使用分隔符时被抛出的问题,RemoteMediator
并且在将返回的远程负载endOfPagination
仍在运行时触发了无效 (I3a260)
推荐阅读
- gradle - 我如何用 gradle 构建 FatJar(没有贬低的功能)
- docker - Docker -v 标志转换为 AWS ECS 任务定义
- python - 一个简单的“Hello”在 python 套接字中打印,如“hellohellohellohello”
- python -
:聚合()缺少1个必需的位置参数:'func_or_funcs' - gitlab - 在 GitLab UI 中将组变量分配给项目变量
- java - Hibernate - 驼峰表的@Formula 问题
- mongodb - 将多个对象插入带有条件的嵌套数组中
- sql - HttpRequest 主机漏洞
- azure - 将本地 SFTP 文件夹中的数据引入 Azure SQL 数据库(Azure 数据工厂)
- google-cloud-platform - 仅将具有特定文件扩展名的文件夹从 Google Cloud Storage Bucket 中的一个文件夹移动到另一个文件夹