es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

“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
Tags:  , , ,

Answer

  1. Avatar for 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 objetos PagingConfig y LoadParams.

    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 en position + 1, pero esto no tiene en cuenta la cantidad real de elementos que se devolvieron. En su lugar, debería establecerse en position + tamaño de carga.

    Además, la consulta utilizada en la función fetchNotifications parece estar omitiendo el parámetro OFFSET, lo que podría provocar que se devuelvan los mismos elementos varias veces. Debería actualizarse para incluir el parámetro OFFSET 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:

    class NotificationsPagingSource(private val useCase: NotificationsUseCase) : PagingSource<int, notifications="">() {
    
        override suspend fun load(params: LoadParams<int>): LoadResult<int, notifications=""> {
            val page = params.key ?: 0
            val pageSize = params.loadSize
    
            val notifications = useCase.fetchNotifications(pageSize, pageSize * page)
            return LoadResult.Page(
                data = notifications,
                prevKey = if (page == 0) null else page - 1,
                nextKey = if (notifications.isEmpty()) null else page + 1
            )
        }
    
        override fun getRefreshKey(state: PagingState<int, notifications="">): Int? {
            return state.anchorPosition
        }
    }
    

    Y aquí hay una implementación actualizada para la clase NotificationsDao:

    @Query("SELECT * FROM notifications ORDER BY createdAt DESC LIMIT :limit OFFSET :offset")
    fun fetchNotifications(limit: Int, offset: Int): List<notifications>
    

    Con estos cambios, los datos deberían cargarse correctamente y no se duplicarán varias veces.</int,></int,></int,>

Comments are closed.