0

In my UI I have a recyclerview and a chip group. Each chip represents different calls to retrofit. When i click on a chip, I expect that recyclerview will disappear until new data is loaded. RV disappears indeed, but when it displays again it shows an old data for less then a second and only then changes to a new data. For the reference i was using paging codelab -> https://developer.android.com/codelabs/android-paging#0

How can i fix it?

FRAGMENT

I observe paging data and load state like this:

    viewLifecycleOwner.lifecycleScope.launch {
        pagingData.collectLatest(customAdapter::submitData)
    }


    viewLifecycleOwner.lifecycleScope.launch {
        customAdapter.loadStateFlow.collect { loadState ->
            header.loadState = loadState.mediator
                ?.refresh
                ?.takeIf { it is LoadState.Error && customAdapter.itemCount > 0 }
                ?: loadState.prepend

            val isListEmpty = loadState.refresh is LoadState.NotLoading && customAdapter.itemCount == 0
            emptyList.isVisible = isListEmpty
            recyclerView.isVisible = loadState.mediator?.refresh is LoadState.NotLoading ||  loadState.source.refresh is LoadState.NotLoading
            progressBar.isVisible = loadState.mediator?.refresh is LoadState.Loading
            retryButton.isVisible = loadState.mediator?.refresh is LoadState.Error && customAdapter.itemCount == 0
            val errorState = loadState.source.append as? LoadState.Error
                ?: loadState.source.prepend as? LoadState.Error
                ?: loadState.append as? LoadState.Error
                ?: loadState.prepend as? LoadState.Error
            errorState?.let {
                Toast.makeText(
                    requireContext(),
                    "\uD83D\uDE28 Wooops ${it.error}",
                    Toast.LENGTH_LONG
                ).show()
            }
        }
    }

Here is how I change category:

        chip1.setOnClickListener {
            viewLifecycleOwner.lifecycleScope.launch {
                onCategoryChanged(CustomAction.ChangeCategory(category = CustomCategory.A))
            }
        }
        chip2.setOnClickListener {
            viewLifecycleOwner.lifecycleScope.launch {
                customAdapter.refresh()
                onCategoryChanged(CustomAction.ChangeCategory(category = CustomCategory.B))
            }
        }

VIEWMODEL

Here is how i update my paging data based on state category in view model:

    val actionStateFlow = MutableSharedFlow<CustomAction>()
    val changedCategory = actionStateFlow
        .filterIsInstance<CustomAction.ChangeCategory>()
        .distinctUntilChanged()
        .onStart { emit(CustomAction.ChangeCategory(category = initialCategory)) }
    pagingDataFlow = state
        .flatMapLatest {
            when (it.category) {
                CustomCategory.A -> searchA(query = it.query)
                CustomCategory.B -> searchB(query = it.query)
            }
        }
        .cachedIn(viewModelScope)

private fun searchA(query: String): Flow<PagingData<CustomItem>> {
    return repository.getA(query = query).map { pagingData ->
        pagingData.map { it.mapToDomainModel() }
    }
}

private fun searchB(query: String): Flow<PagingData<CustomItem>> {
    return repository.getB(query = query).map { pagingData ->
        pagingData.map { it.mapToDomainModel() }
    }
}
ParSerDev
  • 1
  • 1

1 Answers1

0

I'm assuming you're using a RemoteMediator. It seems this is the Paging3 library's intended (albeit counterintuitive) functionality.

The answer lies in this paragraph of the documentation here:

RemoteMediator implementations can also override the initialize() method to check whether cached data is out of date and decide whether to trigger a remote refresh. This method runs before any loading is performed, so you can manipulate the database (for example, to clear old data) before triggering any local or remote loads.

I myself had made it so that the initialize() method always returns LAUNCH_INITIAL_REFRESH which I assumed would always make the Pager "start from scratch", but it seems that if there's something cached in the database, it will still show that data and then trigger a refresh which in turn should clear all data from database and insert new one.

SOLUTION: in the initialize() method of RemoteMediator clear the database of cached items.

FOOD FOR THOUGHT: Consider your use case. This is advice both for you and myself, to be honest :) If the data you fetch depends on a query parameter, does it need caching? For an offline-first app, you would cache data that should already be displayed when the app / screen is launched. But if the data depends on a query that user has to enter / select, then it should be perfectly OK to just show a "no connection available" error.

Karolis
  • 53
  • 5