-
Notifications
You must be signed in to change notification settings - Fork 37
RemoteMediator implementation not possible? #273
Description
It seems as if the RemoteMediator class can't be implemented with cash app paging? If this is true, that would be really sad, as it's the most valuable part of the AndroidX paging library that it's trying to mimic.
The issues arise when trying to implement the actual classes:
1.
Type mismatch.
Required:
RemoteMediatorMediatorResult
Found:
RemoteMediatorMediatorResultSuccess
RemoteMediatorMediatorResult is the expected return type, but can't be initialised. The natural inclination is to use the RemoteMediatorMediatorResultSuccess (which can be initialised) or RemoteMediatorMediatorResultError (which can be initialised), but the method:
override suspend fun load(
loadType: LoadType,
state: PagingState<String, FeatureItem>
): RemoteMediatorMediatorResultdoesn't accept it.
The same issue arises when implementing the Pager. Methods expects: PagingSourceLoadResult:
override suspend fun load(params: PagingSourceLoadParams<String>): PagingSourceLoadResult<String, FeatureItem>Naturally, using the class PagingSourceLoadResultPage (instead of the original LoadResult.Page from AndroidX Paging, doesn't work for the same reason.
Here's the full codebase of all the classes:
package com.feature.data.remote.mediators
import app.cash.paging.LoadType
import app.cash.paging.Pager
import app.cash.paging.PagingConfig
import app.cash.paging.PagingData
import app.cash.paging.PagingSource
import app.cash.paging.PagingSourceLoadParams
import app.cash.paging.PagingSourceLoadResult
import app.cash.paging.PagingSourceLoadResultPage
import app.cash.paging.PagingState
import app.cash.paging.RemoteMediator
import app.cash.paging.RemoteMediatorMediatorResult
import app.cash.paging.RemoteMediatorMediatorResultSuccess
import app.cash.sqldelight.db.Closeable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
class FeatureRemoteMediator(
private val remoteKeysDao: RemoteKeysDao,
private val featureDao: FeatureDao,
private val api: FeatureApi
): RemoteMediator<String, FeatureItem>() {
override suspend fun load(
loadType: LoadType,
state: PagingState<String, FeatureItem>
): RemoteMediatorMediatorResult {
val currentPage = when (loadType) {
LoadType.REFRESH -> {
val remoteKey = getRemoteKeyClosestToCurrentPosition(state)
remoteKey?.nextPage
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstTime(state)
val prevPage = remoteKeys?.previousPage ?: return RemoteMediatorMediatorResultSuccess(endOfPaginationReached = remoteKeys != null)
prevPage
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastTime(state)
val nextPage =
remoteKeys?.nextPage ?: return RemoteMediatorMediatorResultSuccess(endOfPaginationReached = remoteKeys != null)
nextPage
}
else -> {
""
}
}
val response = api.getRemoteItems(
nextPage = currentPage,
pageSize = state.config.pageSize
)
featureDao.insertItems(items = response.items)
val remoteKeys = response.items.map { RemoteKeyEntity(id = "rand", objectId = it.id, cacheId = "_cacheid", nextPage = "", previousPage = "", createdAt = 0) }
remoteKeysDao.insertKeys(list = remoteKeys)
return RemoteMediatorMediatorResultSuccess(endOfPaginationReached = response.nextKey == null)
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<String, FeatureItem>,
): RemoteKeyEntity? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { objectId ->
remoteKeysDao.getKeyForItem(itemId = objectId, cacheId = "_cacheId")
}
}
}
private suspend fun getRemoteKeyForFirstTime(
state: PagingState<String, FeatureItem>,
): RemoteKeyEntity? {
return state.pages.firstOrNull {
it.data.isNotEmpty()
}?.data?.firstOrNull()?.let { featureItem ->
featureItem.id.let { remoteKeysDao.getKeyForItem(itemId = it, cacheId = "_cacheId") }
}
}
private suspend fun getRemoteKeyForLastTime(
state: PagingState<String, FeatureItem>,
): RemoteKeyEntity? {
return state.pages.lastOrNull {
it.data.isNotEmpty()
}?.data?.lastOrNull()?.let { featureItem ->
featureItem.id.let { remoteKeysDao.getKeyForItem(itemId = it, cacheId = "_cacheId") }
}
}
}
class FeatureRepository(
private val remoteKeysDao: RemoteKeysDao,
private val dao: FeatureDao,
private val api: FeatureApi
) {
fun getPagingList(): Flow<PagingData<FeatureItem>> {
val pager = Pager(
config = PagingConfig(
pageSize = 12
),
pagingSourceFactory = {
dao.getPagingSource()
},
remoteMediator = FeatureRemoteMediator(
remoteKeysDao = remoteKeysDao,
featureDao = dao,
api = api
)
)
return pager.flow
}
}
class FeatureApi {
suspend fun getRemoteItems(nextPage: String?, pageSize: Int): PaginationResponse<FeatureItem> {
return PaginationResponse(
items = emptyList(),
nextKey = null,
prevKey = null
)
}
}
class RemoteKeysDao() {
suspend fun insertKeys(list: List<RemoteKeyEntity>) {}
suspend fun getKeyForItem(itemId: String, cacheId: String): RemoteKeyEntity {
TODO()
}
}
class FeatureDao {
fun getPagingSource(): PagingSource<String, FeatureItem> {
val pagingSource = FeatureItemPagingSource()
return pagingSource
}
suspend fun insertItems(items: List<FeatureItem>) {
TODO()
}
}
data class FeatureItem(
val id: String,
val hello: String
)
class GetFeatureItemsUseCase(
private val repository: FeatureRepository
) {
operator fun invoke(): Flow<PagingData<FeatureItem>> {
return repository.getPagingList()
}
}
class FeatureItemPagingSource(): PagingSource<String, FeatureItem>() {
override suspend fun load(params: PagingSourceLoadParams<String>): PagingSourceLoadResult<String, FeatureItem> {
val allItems = listOf<FeatureItem>(
FeatureItem(
id = "id",
hello = "Yes!"
)
)
return PagingSourceLoadResultPage(
data = allItems,
nextKey = params.key,
prevKey = null
)
}
override fun getRefreshKey(state: PagingState<String, FeatureItem>): String? {
TODO()
}
}
data class RemoteKeyEntity(
val id: String,
val objectId: String,
val cacheId: String,
val nextPage: String?,
val previousPage: String?,
val createdAt: Long = 0
)
data class PaginationResponse<T>(
val items: List<T>,
val nextKey: String?,
val prevKey: String?
)