0

I have a list of items from API. I'm using Paging3 with RemoteMedatior to maintain network with local caching in database using Room. My issue is when data is loading recyclerView looks messy and glitchy, items appear in the middle of the list and above visible ones, and recyclerView doesn't scroll to the top of the list. Looks like this.
I've tried with various values for pageSize, maxSize, and prefetchDistance for pageConfig but it doesn't help. setHasFixedSize on recylerview also doesn't work. Flow is collected with collectLatest in the fragment. I've tried also normal collect and changing flow into liveData. Same result. Any idea how to correct this?

load function in RemoteMediator

@ExperimentalPagingApi
class RecipesMediator(
    private val recipesApi: RecipesApi,
    private val appDatabase: RecipesDatabase,
    private val queries: HashMap<String, String>,
) : RemoteMediator<Int, Recipe>() {
    override suspend fun load(loadType: LoadType, state: PagingState<Int, Recipe>): MediatorResult {

    val queryId = queries[QUERY_DIET] + queries[QUERY_MEAL]

    val page = when (loadType) {
        LoadType.REFRESH -> 0
        LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
        LoadType.APPEND -> appDatabase.remoteKeysDao().remoteKeysRecipeId(queryId).nextKey
    }
    
    queries[QUERY_OFFSET] = page.toString()
    queries[QUERY_NUMBER] = "20"

    try {
        val response = recipesApi.getRecipes(queries)

        appDatabase.withTransaction {
            if (loadType == LoadType.REFRESH) {
                appDatabase.remoteKeysDao().clearRemoteKeys()
                appDatabase.recipesDao().clearRecipes()
            }

            val nextKey = page + 20
            val keys = response.recipes.map {

                RemoteKeys(query = queryId, nextKey = nextKey)
            }
            appDatabase.remoteKeysDao().insertAll(keys)
            appDatabase.recipesDao().insertAllRecipes(response.recipes)
        }
        return MediatorResult.Success(endOfPaginationReached = response.recipes.isEmpty())
    } catch (exception: IOException) {
        return MediatorResult.Error(exception)
    } catch (exception: HttpException) {
        return MediatorResult.Error(exception)
    }
}

I use string for remote key ids, because when i used item id from API = LoadType.Append was called many times and only first page was loaded. Its explained in this issue

nextPage is "page + 20" because this API (Spoonacular) doesn't provide pagination, although provides parameters offset (number of results to skip) and number(number of expected results), so i used them for my paging.

UI states in fragment

 recipesAdapter.addLoadStateListener { loadState ->
        binding.apply {
            recipesProgressBar.isVisible = loadState.mediator?.refresh is LoadState.Loading
            recipesProgressBar.isVisible = loadState.source.refresh is LoadState.Loading

            recipesRecyclerView.isVisible =loadState.mediator?.refresh is LoadState.NotLoading
            recipesRecyclerView.isVisible =loadState.source.refresh is LoadState.NotLoading

            buttonRecipesRetry.isVisible =loadState.mediator?.refresh is LoadState.Error
            buttonRecipesRetry.isVisible =loadState.source.refresh is LoadState.Error

            textViewRecipesError.isVisible =loadState.mediator?.refresh is LoadState.Error
            textViewRecipesError.isVisible =loadState.source.refresh is LoadState.Error

            if (loadState.source.refresh is LoadState.NotLoading &&
                loadState.mediator?.refresh is LoadState.NotLoading &&
                loadState.append.endOfPaginationReached &&
                recipesAdapter.itemCount < 1
            ) {
                recipesRecyclerView.isVisible = false
                textViewEmpty.isVisible = true
            } else {
                textViewEmpty.isVisible = false
            }
        }
    }

Remote Keys

@Entity(tableName = "remoteKeys")
data class RemoteKeys
(@PrimaryKey val query: String, val nextKey: Int)

Remote Keys Dao

@Dao
interface RemoteKeysDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(remoteKey: List<RemoteKeys>)

@Query("SELECT * FROM remoteKeys WHERE `query` = :query")
suspend fun remoteKeysId(query: String): RemoteKeys

@Query("DELETE FROM remoteKeys")
suspend fun clearRemoteKeys()
}
  • How are you sorting the list when you query the cache from PagingSource? This is usually attributed to remote fetching items out of order than your PagingSource expects – dlam Sep 18 '21 at 22:37
  • hi Dlam. Thanks for comment. Well, im not sorting it at all. I'm getting in directly as it is from database. Snippet from my dao: @Query("SELECT * FROM foodRecipe") fun getRecipesPagingSource(): PagingSource – puszkinowski Sep 20 '21 at 08:34
  • 1
    That seems problematic because you have no guarantees on the order you load the data in. The way RemoteMediator works is by updating the backing dataset and invalidating Paging, so it reloads from the DB to pick up the new items. This maintains a single source of truth, but if you don't sort the results, you might end up with the newly fetched items appearing in the middle of the list. You want something like `@Query("SELECT * FROM foodRecipe ORDER BY id")`. – dlam Sep 21 '21 at 17:55
  • If your backend doesn't give you a canonical way to order your items, you can always track yourself based on the index from the response - we have an example of this on github here: https://github.com/android/architecture-components-samples/blob/main/PagingWithNetworkSample – dlam Sep 21 '21 at 17:56

0 Answers0