分页 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)