How can we mutate items loaded using Paging 3 android?
S

4

7

I am loading posts from network and for this i'm using Paging 3, but now problem is that my list items contains Like/Dislike button, suppose Like is clicked then how can i update data for that item without reloading whole dataset?

i have read this android-pagedlist-updates, but it seems that this is for older Paging 2 or 1, So what is perfect way to to achieve this in Paging 3

Seigneury answered 30/7, 2020 at 11:0 Comment(1)
were you able to solve the issue?Gott
H
0

In Paging3 you still need to rely on PagingSource.invalidate to submit updates, this isn't so much about immutability, but more about having a single source of truth.

In general, the correct way to do this is to update the backing dataset and call invalidate, which will trigger a REFRESH + DiffUtil that shouldn't cause any UI changes, but guarantees that if that page is dropped and refetched, the loaded pages will still be up-to-date. The easiest way to do this is to use a PagingSource implementation that already has self-invalidation built-in, like the one provided by Room, and just update the corresponding row onClick of the like / dislike button.

There is an open bug tracking the work to support highly frequent, granular updates to the list with a Flow<>, which you can follow here if this is your use case: https://issuetracker.google.com/160232968

Heterolecithal answered 30/7, 2020 at 17:4 Comment(2)
I am thinking to use RemoteMediator as explained here developer.android.com/topic/libraries/architecture/paging/…, do you also mean to use something similarSeigneury
Regardless of whether you use RemoteMediator, Pager still requires a lambda which produces a PagingSource in its constructor. All data loaded and presented is still based off of what PagingSource returns. RemoteMediator simply allows you to make remote calls to fetch additional data (which would then be stored for PagingSource) in boundary conditions and communicate that load state for synchronous updates on your adapter.Heterolecithal
I
0

I overcome this challenge with below mechanism. Maintain the internal Hashmap to hold key and object, keep this internal hashmap inside your pagedlist adapter. As the list scrolls , you will add remote like/dislike into internal hashmap as initial status by using its something unique key, since the key is unique, you will not going to duplicate and then you refer this internal hashmap for your update UI.

onClick listener of like and dislike will update this internal hashmap. again internal hashmap is reference for UI update.

Solution is simple - collecting helpful data on another internal hashmap for later manipulation.

Inhalator answered 31/7, 2020 at 6:46 Comment(0)
A
0

I found a work-around with which u can achieve this, giving some of the background of the code-base that I am working with:

  • I have a PagingDataAdapter (Pagination-3.0 adapter) as a recycler view adapter.
  • I have a viewmodel for fragment
  • I have a repository which returns flow of PaginationData
  • And exposing this flow as liveData to fragment

Code for repository:

override fun getPetsList(): Flow<PagingData<Pets>> {
    return Pager(
        config = PagingConfig(
            pageSize = 15,
            enablePlaceholders = false,
            prefetchDistance = 4
        ),
        pagingSourceFactory = {
            PetDataSource(petService = petService)
        }
    ).flow
}

Code for viewmodel:

// create a hashmap that stores the (key, value) pair for the object that have changed like (id:3, pet: fav=true .... )

viewModelScope.launch {
            petRepository.getPetsList()
                .cachedIn(viewModelScope)
                .collect {
                    _petItems.value = it
                }
        }

Now the code for fragment where mapping and all the magic happens

viewModel.petItems.observe(viewLifecycleOwner) { pagingData ->
        val updatedItemsHashMap = viewModel.updatedPetsMap
        val updatedPagingData = pagingData.map { pet ->
            if (updatedItemsHashMap.containsKey(pet.id))
                updatedItemsHashMap.getValue(pet.id)
            else
                pet
        }
        viewLifecycleOwner.lifecycleScope.launch {
            petAdapter.submitData(updatedPagingData)
        }
    }

So that is how you can achieve this, the crux is to do mapping of pagingData which is emitted from repository.

Things which won't work:

_petItems.value = PagingData.from(yourList)

This won't work because as per docs this is used for static list, and you would loose the pagination power that comes with pagination 3.0. So mapping pagingData seems the only way.

Ascham answered 1/9, 2021 at 4:26 Comment(0)
W
0

To achieve this with Paging 3 and without room database, we can create a mutable Flow of data list in the ViewModel that contains the items we want to manipulate. When observing the Flow of Paging 3, we can combine it with our local Flow and find the item we want to change. We can then change it before submitting it to the view to observe.

Here's an example of a simplified ViewModel that demonstrates this solution:

class ExampleViewModel(
    private val likePostUseCase: LikePostUseCase,
    private val unlikePostUseCase: UnlikePostUseCase,
    private val getPaginingPostsUseCase: GetPostsUseCase
) : ViewModel() {

    private val _localDataList = MutableStateFlow(listOf<MyData>())

    // Observe the Paging 3 Flow and combine it with the local Flow
    val combinedDataList = getPaginingPostsUseCase().cachedIn(viewModelScope).combine(_localDataList) { paging, local ->
        // Find and update the desired item in the list
        paging.map {
            if (it.id == local.id) local
            else  it
        }
    }

    // Method to update the item in the local Flow when the post is liked
    fun likePost(item: MyData) {
        likePostUseCase(item.id)
        val updatedItem = item.copy(isLiked = true) // or use the returned item from likePostUseCase.
        val newList = _localDataList.value.filterNot { it.id == updatedItem.id } // remove old version if any.
        _localDataList.value = newList + updatedItem // this triggers flow combine and updates your recyclerview without loading all pagination data again.
    }

    // Method to update the item in the local Flow when the post is unliked
    fun unlikePost(item: MyData) {
        unlikePostUseCase(item.id)
        val updatedItem = item.copy(isLiked = false)
        val newList = _localDataList.value.filterNot { it.id == updatedItem.id } // remove old version if any.
        _localDataList.value = newList + updatedItem
    }
}

Waldon answered 5/4, 2023 at 4:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.