Как мы можем изменить элементы, загруженные с помощью Paging 3 android?
Я загружаю сообщения из сети, и для этого я использую Paging 3, но теперь проблема в том, что мои элементы списка содержат кнопку Like/Dislike, предположим, что нажата кнопка Like, тогда как я могу обновить данные для этого элемента без перезагрузки всего набора данных?
Я прочитал эти обновления android-pagedlist, но кажется, что это для более старых Paging 2 или 1, так что это идеальный способ добиться этого в Paging 3
4 ответа
Чтобы добиться этого с помощью Paging 3 и без базы данных помещений, мы можем создать изменяемый поток списка данных в ViewModel, содержащий элементы, которыми мы хотим манипулировать. Наблюдая за потоком пейджинга 3, мы можем объединить его с нашим локальным потоком и найти элемент, который мы хотим изменить. Затем мы можем изменить его, прежде чем отправить его на рассмотрение для наблюдения.
Вот пример упрощенной ViewModel, демонстрирующей это решение:
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
}
}
Я преодолеваю эту проблему с помощью механизма ниже. Сохраните внутреннюю хэш-карту для хранения ключа и объекта, сохраните эту внутреннюю хэш-карту внутри адаптера списка страниц. По мере прокрутки списка вы добавите удаленное лайк / не нравится во внутреннюю хэш-карту в качестве начального статуса, используя его уникальный ключ, поскольку ключ уникален, вы не собираетесь дублировать, а затем вы ссылаетесь на этот внутренний хэш-карту для своего пользовательского интерфейса обновления.
onClick, которому нравится и не нравится, обновит эту внутреннюю хеш-карту. снова внутренний хэш-карта является ссылкой для обновления пользовательского интерфейса.
Решение простое - сбор полезных данных на другой внутренней хэш-карте для последующих манипуляций.
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 at view layer (fragment in this case), and this mapping should be saved in viewmodel as data in viewmodel persist across all orientation changes. You can use coroutines to do this mapping off the main thread.
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.
В Paging3 вам все еще нужно полагаться на PagingSource.invalidate
для отправки обновлений речь идет не столько о неизменности, сколько о едином источнике истины.
В общем, правильный способ сделать это - обновить набор данных резервного копирования и вызвать invalidate
, который вызовет REFRESH + DiffUtil, который не должен вызывать никаких изменений пользовательского интерфейса, но гарантирует, что если эта страница будет отброшена и повторно загружена, загруженные страницы все равно будут актуальными. Самый простой способ сделать это - использовать реализацию PagingSource, которая уже имеет встроенную функцию самоотключения, например, предоставленную Room, и просто обновить соответствующую строку onClick кнопки "Нравится / Не нравится".
Существует открытая ошибка отслеживания работы для поддержки очень частых и детальных обновлений списка с помощью Flow<>, за которым вы можете следить здесь, если это ваш случай использования: https://issuetracker.google.com/160232968