Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RemoteMediator implementation not possible? #273

Open
JohanAlbrectsen opened this issue May 11, 2024 · 2 comments
Open

RemoteMediator implementation not possible? #273

JohanAlbrectsen opened this issue May 11, 2024 · 2 comments

Comments

@JohanAlbrectsen
Copy link

JohanAlbrectsen commented May 11, 2024

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>
    ): RemoteMediatorMediatorResult

doesn'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?
)
@JohanAlbrectsen
Copy link
Author

@JakeWharton
Hi Jake,
I understand no one is working on this repo. Does that mean issues aren't getting solved / answered anymore? Is it temporary that no one is working on it anymore? And is there a newer paging library that's being worked on? Would love if someone could guide me on this issue #273.
Kind regards

@JakeWharton
Copy link
Collaborator

Does that mean issues aren't getting solved / answered anymore?

Yes

Is it temporary that no one is working on it anymore?

I don't know. All I can tell you is the current status.

And is there a newer paging library that's being worked on?

Not by us. We are using this repo as-is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants