分页 3 - IllegalArgumentException:完成前置状态后的附加前置事件
Paging 3 - IllegalArgumentException: Additional prepend event after prepend state is done
使用 Paging 3(基于 this 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.emit(Collect.kt:135)
at androidx.paging.PagingDataKt$map$$inlined$transform.emit(Collect.kt:136)
at kotlinx.coroutines.flow.FlowKt__ChannelsKt.emitAllImpl$FlowKt__ChannelsKt(Channels.kt:61)
at kotlinx.coroutines.flow.FlowKt__ChannelsKt$emitAllImpl.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.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 class,如果有帮助,我可以添加其他 classes。
@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 示例项目中对其进行了测试,它正在运行。
Fix for IllegalArgumentException
being throw when using separators with RemoteMediator
and an invalidate is triggered while a remote load that would return endOfPagination
is still running (I3a260)
使用 Paging 3(基于 this 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.emit(Collect.kt:135)
at androidx.paging.PagingDataKt$map$$inlined$transform.emit(Collect.kt:136)
at kotlinx.coroutines.flow.FlowKt__ChannelsKt.emitAllImpl$FlowKt__ChannelsKt(Channels.kt:61)
at kotlinx.coroutines.flow.FlowKt__ChannelsKt$emitAllImpl.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.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 class,如果有帮助,我可以添加其他 classes。
@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 示例项目中对其进行了测试,它正在运行。
Fix for
IllegalArgumentException
being throw when using separators withRemoteMediator
and an invalidate is triggered while a remote load that would returnendOfPagination
is still running (I3a260)