“Multiples veces se presenta un elemento de lista repetido en un recycler-view al usar Paging v3.”
He implementado android-paging v3 siguiendo https://proandroiddev.com/paging-3-easier-way-to-pagination-part-1-584cad1f4f61 & https://proandroiddev.com/how-to-use-the-paging-3-library-in-android-part-2-e2011070a37d. Pero veo que los datos se están poblado varias veces incluso cuando solo hay 3 registros en la base de datos local.
Pantalla de lista de notificaciones
¿Alguien puede sugerir qué estoy haciendo mal? Gracias de antemano.
Mi código es el siguiente:
NotificationsFragment
class NotificationsFragment : Fragment() {
private lateinit var binding: FragmentNotificationsBinding
private val alertViewModel: NotificationsViewModel by viewModel()
private val pagingAdapter by lazy { AlertsPagingAdapter() }
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentNotificationsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onResume() {
super.onResume()
(activity as MainActivity).setUpCustomToolbar(
getString(R.string.alerts),
""
)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner
initRecyclerView()
}
private fun initRecyclerView() {
binding.rvAlerts.apply {
adapter = pagingAdapter.withLoadStateFooter(AlertLoadStateAdapter {})
layoutManager = LinearLayoutManager(requireContext())
}
lifecycleScope.launch {
alertViewModel.alertListFlow.collectLatest { pagingData ->
pagingAdapter.submitData(
pagingData
)
}
}
}
}
NotificationsViewModel
class NotificationsViewModel(private val useCase: NotificationsUseCase) : BaseViewModel() {
val alertListFlow = Pager(PagingConfig(1)) { NotificationsPagingSource(useCase) }
.flow
.cachedIn(viewModelScope)
}
NotificationsPagingSource
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.example.demo.model.entity.Notifications
import com.example.demo.NotificationsUseCase
class NotificationsPagingSource(private val useCase: NotificationsUseCase) : PagingSource() {
private companion object {
const val INITIAL_PAGE_INDEX = 0
}
override suspend fun load(params: LoadParams): LoadResult {
val position = params.key ?: INITIAL_PAGE_INDEX
val randomNotifications: List = useCase.fetchNotifications(params.loadSize)
return LoadResult.Page(
data = randomNotifications,
prevKey = if (position == INITIAL_PAGE_INDEX) null else position - 1,
nextKey = if (randomAlerts.isNullOrEmpty()) null else position + 1
)
}
override fun getRefreshKey(state: PagingState): Int? {
// Necesitamos obtener la clave anterior (o la siguiente si la anterior es nula) de la página
// que estuvo más cerca del índice más recientemente accedido.
// La posición de anclaje es el índice más recientemente accedido
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
}
PagingAdapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
class NotificationsPagingAdapter :
PagingDataAdapter(NotificationsEntityDiff()) {
override fun onBindViewHolder(holder: ItemNotificationsViewHolder, position: Int) {
getItem(position)?.let { userPostEntity -> holder.bind(userPostEntity) }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemNotificationsViewHolder {
return ItemNotificationsViewHolder(
ItemLayoutNotificationsBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
/**
* Viewholder for each Notifications layout item
*/
inner class ItemNotificationsViewHolder(private val binding: ItemLayoutNotificationsBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(alert: Notifications) {
binding.tvMessage.text = alert.title
}
}
class NotificationsEntityDiff : DiffUtil.ItemCallback() {
override fun areItemsTheSame(oldItem: Notifications, newItem: Notifications): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Alert, newItem: Notifications): Boolean =
oldItem == newItem
}
}
NotificationsLoadStateAdapter
class NotificationsLoadStateAdapter(
private val retry: () -> Unit
) : LoadStateAdapter() {
override fun onBindViewHolder(holder: LoadStateViewHolder, loadState: LoadState) {
val progress = holder.itemView.load_state_progress
val btnRetry = holder.itemView.load_state_retry
val txtErrorMessage = holder.itemView.load_state_errorMessage
btnRetry.isVisible = loadState !is LoadState.Loading
// txtErrorMessage.isVisible = loadState !is LoadState.Loading
progress.isVisible = loadState is LoadState.Loading
if (loadState is LoadState.Error) {
// txtErrorMessage.text = loadState.error.localizedMessage
}
btnRetry.setOnClickListener {
retry.invoke()
}
}
override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): LoadStateViewHolder {
return LoadStateViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.layout_load_state_view, parent, false)
)
}
class LoadStateViewHolder(private val view: View) : RecyclerView.ViewHolder(view)
}
Dao
@Query("SELECT * FROM notifications ORDER BY createdAt DESC LIMIT :size")
fun fetchNotifications(size: Int): List
davy.ai
El problema de que los datos se estén poblado varias veces, incluso cuando solo hay 3 registros en la base de datos local, podría deberse a que la clase
NotificationsPagingSource
no está configurada adecuadamente para manejar los objetosPagingConfig
yLoadParams
.Una posible solución sería modificar la implementación de
NotificationsPagingSource
para manejar correctamente el tamaño de carga y la clave siguiente. En la implementación actual, la clave siguiente se establece enposition + 1
, pero esto no tiene en cuenta la cantidad real de elementos que se devolvieron. En su lugar, debería establecerse enposition + tamaño de carga
.Además, la consulta utilizada en la función
fetchNotifications
parece estar omitiendo el parámetroOFFSET
, lo que podría provocar que se devuelvan los mismos elementos varias veces. Debería actualizarse para incluir el parámetroOFFSET
en función del tamaño de carga y el número de página.Aquí hay una implementación actualizada para la clase
NotificationsPagingSource
:Y aquí hay una implementación actualizada para la clase
NotificationsDao
:Con estos cambios, los datos deberían cargarse correctamente y no se duplicarán varias veces.</int,></int,></int,>