3

I am trying to cache list from api response and sync the data with the server, I followed up with Codelab to create the single source of truth as android documentation shows up using paging 3, I followed the steps and i was surprised with the results, but when i tried to cache the paging data i'v got this thrown error while just the first ever running and continue work when the app crashes or while the first running without internet connection
InvalidObjectException("Remote key and the prevKey should not be null")
and it seams came from :

private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, MohItem>): RemoteKeys? {
        return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
            ?.let { mohItem ->
                database.remoteKeysDao().remoteKeysId(mohItem.id)
            }
    }


It's convenience for me that it's since it's trying to insert keys which isn't received from empty or null list as the snipped code shows up :

@ExperimentalPagingApi
class MohRemoteMediator(
    private val context: Context,
    private val database: IbnsinaDatabase,
    private val apiService: ApiService,
    private val query: MohSearchQueryRequest

) : RemoteMediator<Int, MohItem>() {
    override suspend fun load(
        loadType: LoadType,
        state: PagingState<Int, MohItem>
    ): MediatorResult {
        val page = when (loadType) {
            LoadType.REFRESH -> {
                val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
                remoteKeys?.nextKey?.minus(1) ?: Constants.PAGING_STARTING_PAGE_INDEX
            }
            LoadType.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 -> {
                val remoteKeys = getRemoteKeyForLastItem(state)
                if (remoteKeys?.nextKey == null) {
                    throw InvalidObjectException("Remote key should not be null for $loadType")
                }
                remoteKeys.nextKey
            }
        }
        try {
            val apiResponse = apiService.getMohList(
                pageIndex = page,
                title = query.title,
                number = query.number,
                month = query.month,
                year = query.year
            )

            val mohs = apiResponse.data ?: emptyList()
            val endOfPaginationReached = mohs.isEmpty()

            database.withTransaction {
                if (loadType == LoadType.REFRESH) {
                    database.remoteKeysDao().clearRemoteKeys()
                    database.mohDao().clearMohs()
                }
                val prevKey = if (page == Constants.PAGING_STARTING_PAGE_INDEX) null else page - 1
                val nextKey = if (endOfPaginationReached) null else page + 1
                val keys = mohs.map {
                    RemoteKeys(mohId = it.id, prevKey = prevKey, nextKey = nextKey)
                }

                database.remoteKeysDao().insertAll(keys)
                database.mohDao().insertAllMohs(mohs)
            }
            return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
        } catch (exception: IOException) {
            return MediatorResult.Error(Throwable(context.getString(R.string.no_internet_connection)))
        } catch (exception: HttpException) {
            return MediatorResult.Error(exception)
        }
    }

    private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, MohItem>): RemoteKeys? {
        return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
            ?.let { repo ->
                // Get the remote keys of the first items retrieved
                database.remoteKeysDao().remoteKeysId(repo.id)
            }
    }

    private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, MohItem>): RemoteKeys? {
        return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
            ?.let { mohItem ->
                database.remoteKeysDao().remoteKeysId(mohItem.id)
            }
    }

    private suspend fun getRemoteKeyClosestToCurrentPosition(
        state: PagingState<Int, MohItem>
    ): RemoteKeys? {
        return state.anchorPosition?.let { position ->
            state.closestItemToPosition(position)?.id?.let { repoId ->
                database.remoteKeysDao().remoteKeysId(repoId)
            }
        }
    }
}
Hadi Ahmed
  • 41
  • 1
  • 5

2 Answers2

1

Do you actually want to support PREPEND here or is the first page you load always going to be the first page (no prepend from network)?

This code in your RemoteMediator: val prevKey = if (page == Constants.PAGING_STARTING_PAGE_INDEX) null else page - 1

seems to imply that your prevKey will always be null after initial REFRESH so that in your load method,

            LoadType.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
            }

will always throw after initial REFRESH?

If you don't support PREPEND from network (you can still set maxSize and drop, then reload from PagingSource), then the simplest fix would be to return MediatorResult.Success(true) on PREPEND in RemoteMediator.load() immediately, instead of trying to fetch a key that should never exist.

dlam
  • 3,547
  • 17
  • 20
  • So can I set a `maxSize` value even if `PREPEND` returns `MediatorResult.Success`? When I try it I'm getting an `IndexOutOfBoundsException` in `PageFetcherSnapshot.doLoad` if I call `scrollToPosition(0)`. I don't get this crash if I scroll up manually tho – Florian Walther Jan 26 '21 at 09:16
  • There is an open bug for that here: https://issuetracker.google.com/178448265. With a fix that should land in the next next release you can follow here: https://android-review.googlesource.com/c/platform/frameworks/support/+/1559982 – dlam Jan 27 '21 at 00:47
0

This is the issue in all the version of Paging above 3.0.0-alpha02. So you can change your paging library version to 3.0.0-alpha02. I have also created an issue on google issue tracker, you can track the progress of the issue. https://issuetracker.google.com/issues/170025945

Milan Thakor
  • 103
  • 3
  • 7