JackSaf
JackSaf

Reputation: 93

Remote mediator with Room's PagingSource sometimes returns 0 items

I have two feeds in my Android application. One is for friends' posts and the other is recommended ones. So these are first two tabs. Friends feedRecommendations feed

For both I use Pager 3 Remote Mediator and Room's paging source, both tabs use the same database table, they only differ in field "source"(feed/discover). Ui is written in Compose. The problem is when I refresh them simultaneously and switch between these tabs I get 0 item count for paging data for one or sometimes both of them until they both are loaded/refreshed. So these is PagerFactory

 @OptIn(ExperimentalPagingApi::class)
        fun getFeedPager(currentUserId: String): Flow<PagingData<Post>> {
            return Pager(
                config = PagingConfig(
                    pageSize = FEED_PAGE_SIZE,
                    prefetchDistance = FEED_PAGE_SIZE,
                    initialLoadSize = FEED_PAGE_SIZE
                ),
                pagingSourceFactory = {
                    database.postDao().getPostsPagingSource(PostSource.Feed.name)
                },
                remoteMediator = FeedRemoteMediator(
                    database = database,
                    postApi = postApi,
                    profileFirestoreService = profileFirestoreService,
                    source = PostSource.Feed,
                    appPreferencesDatastoreService = appPreferencesDatastoreService,
                    configPreferencesDatastoreService = configPreferencesDatastoreService,
                    followersFirestoreService = followersFirestoreService
                )
            ).flow.map {
                it.map { post ->
                    PostMapper.mapToDomain(post, currentUserId)
                }
            }
        }

This is RemoteMediator

@OptIn(ExperimentalPagingApi::class)
class FeedRemoteMediator(
    private val database: AlyvDatabase,
    private val postApi: PostApi,
    private val source: PostSource,
    private val configPreferencesDatastoreService: ConfigPreferencesDatastoreService,
    private val appPreferencesDatastoreService: AppPreferencesDatastoreService
) : RemoteMediator<Int, PostDb>() {

    private val postRemoteKeysDao: PostRemoteKeysDao = database.remoteKeysDao()
    private val postDao: PostDao = database.postDao()

    override suspend fun initialize(): InitializeAction {
        val cacheTimeout = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS)

        return if (System.currentTimeMillis() - (postRemoteKeysDao.getCreationTime(source.name)
                ?: 0) < cacheTimeout
        ) {
            InitializeAction.SKIP_INITIAL_REFRESH
        } else {
            InitializeAction.LAUNCH_INITIAL_REFRESH
        }
    }

    override suspend fun load(
        loadType: LoadType,
        state: PagingState<Int, PostDb>
    ): MediatorResult {
        return withContext(Dispatchers.IO){
            val page: Int = when (loadType) {
                LoadType.REFRESH -> {
                    val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
                    remoteKeys?.nextKey?.minus(1)?.coerceAtLeast(0) ?: 0
                }

                LoadType.PREPEND -> {
                    val remoteKeys = getRemoteKeyForFirstItem(state)
                    val prevKey = remoteKeys?.prevKey
                    prevKey ?: return@withContext MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
                }

                LoadType.APPEND -> {
                    val remoteKeys = getRemoteKeyForLastItem(state)
                    val nextKey = remoteKeys?.nextKey
                    nextKey ?: return@withContext MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
                }
            }
            try {
                val userId = appPreferencesDatastoreService.getUserId()
                    ?: throw IllegalStateException("User id is missing")
                val request = GetFeedRequest(userId = userId, page = page)
                val url = when (source) {
                    PostSource.Feed -> configPreferencesDatastoreService.getFeedUrl()
                    PostSource.Discover -> configPreferencesDatastoreService.getDiscoverFeedUrl()
                    else -> ""
                } ?: throw IllegalStateException("Feed url is missing")
                val response = postApi.getFeed(url, request)
                val posts = response.body() ?: emptyList()
                val endOfPaginationReached = posts.isEmpty()
                database.withTransaction {
                    val prevKey = if (page > 1) page - 1 else null
                    val nextKey = if (endOfPaginationReached) null else page + 1
                    val remoteKeys = posts.map {
                        RemoteKeyDb(
                            postId = it.postId,
                            prevKey = prevKey,
                            currentPage = page,
                            nextKey = nextKey,
                            source = source.name
                        )
                    }
                    val postsForDb = posts.map { post ->
                            PostMapper.mapToDb(
                                post = post,
                            )  
                    }
                    if (loadType == LoadType.REFRESH) {
                        postRemoteKeysDao.clearRemoteKeys(source.name)
                        postDao.clearAllFromSource(source.name)
                    }
                    postRemoteKeysDao.insertAll(remoteKeys)
                    postDao.saveAll(postsForDb)
                }
                return@withContext MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
            } catch(exception: Exception){
                Log.i("FeedRemoteMediator", "Error: ${exception.message}")
                exception.printStackTrace()
                return@withContext MediatorResult.Error(exception)
            }
        }
    }

And this is dao method

@Query("SELECT * FROM post WHERE source = :source ORDER BY creation_date DESC")
fun getPostsPagingSource(source: String): PagingSource<Int, PostDb>

Is there any way to fix this and not to lose items while refreshing two lists simultaneously?

I tried creating custom paging source and it seemed to work, I could refresh and see both lists, but I get other glitches like LazyVerticalGrid weird jumping, but this is a problem for a different thread.

UPDATE: If I call .collectAsLazyPagingItems() for posts from these tabs not in the screen composable but at topmost composable which contains all navigation and then pass LazyPagingItems down to screens, everything is working perfect but I don't want to leave it that way =(

Upvotes: 3

Views: 79

Answers (0)

Related Questions