After making a simple app with Paging 3 (based on this Google GitHub codelab) my app crashes. When I scroll down, at some point (probably when the new GET function is called). Logcat looks like this:
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
The same error happens in the Google sample app from GitHub but in their case, it happens after scrolling down and then scrolling up. Is there any possibility to see where and what exactly causes this error? I tried to read this logcat error but it says nothing to me. Below is RemoteMediator and Repository class, I can add other classes if it can help.
@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)
}
}
}
}
RepositoryClass:
@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
}
}