Estoy usando la nueva biblioteca de soporte ListAdapter . Aquí está mi código para el adaptador.
class ArtistsAdapter : ListAdapter<Artist, ArtistsAdapter.ViewHolder>(ArtistsDiff()) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return ViewHolder(parent.inflate(R.layout.item_artist)) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.bind(getItem(position)) } class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { fun bind(artist: Artist) { itemView.artistDetails.text = artist.artistAlbums .plus(" Albums") .plus(" \u2022 ") .plus(artist.artistTracks) .plus(" Tracks") itemView.artistName.text = artist.artistCover itemView.artistCoverImage.loadURL(artist.artistCover) } } }Estoy actualizando el adaptador con
musicViewModel.getAllArtists().observe(this, Observer { it?.let { artistAdapter.submitList(it) } })mi clase diferencial
class ArtistsDiff : DiffUtil.ItemCallback<Artist>() { override fun areItemsTheSame(oldItem: Artist?, newItem: Artist?): Boolean { return oldItem?.artistId == newItem?.artistId } override fun areContentsTheSame(oldItem: Artist?, newItem: Artist?): Boolean { return oldItem == newItem } }Lo que sucede es que cuando se llama a submitList la primera vez que el adaptador representa todos los elementos, pero cuando se vuelve a llamar a submitList con propiedades de objeto actualizadas, no se vuelve a representar la vista que ha cambiado.
Vuelve a representar la vista a medida que me desplazo por la lista, que a su vez llama a bindView()
Además, me di cuenta de que llamar a adapter.notifyDatasSetChanged() después de enviar la lista muestra la vista con valores actualizados, pero no quiero llamar a notifyDataSetChanged() porque el adaptador de lista tiene funciones de diferenciación integradas.
¿Me puede ayudar alguien?
Si encuentra algunos problemas al usar
recycler_view.setHasFixedSize(true)
definitivamente deberías revisar este comentario: https://github.com/thoughtbot/expandable-recycler-view/issues/53#issuecomment-362991531
Resolvió el problema de mi lado.
(Aquí hay una captura de pantalla del comentario solicitado)
Hoy también me topé con este "problema". Con la ayuda de la respuesta de insa_c y la solución de RJFares, me hice una función de extensión de Kotlin:
/** * Update the [RecyclerView]'s [ListAdapter] with the provided list of items. * * Originally, [ListAdapter] will not update the view if the provided list is the same as * currently loaded one. This is by design as otherwise the provided DiffUtil.ItemCallback<T> * could never work - the [ListAdapter] must have the previous list if items to compare new * ones to using provided diff callback. * However, it's very convenient to call [ListAdapter.submitList] with the same list and expect * the view to be updated. This extension function handles this case by making a copy of the * list if the provided list is the same instance as currently loaded one. * * For more info see 'RJFares' and 'insa_c' answers on * https://stackoverflow.com/questions/49726385/listadapter-not-updating-item-in-reyclerview */ fun <T, VH : RecyclerView.ViewHolder> ListAdapter<T, VH>.updateList(list: List<T>?) { // ListAdapter<>.submitList() contains (stripped): // if (newList == mList) { // // nothing to do // return; // } this.submitList(if (list == this.currentList) list.toList() else list) }que luego se puede usar en cualquier lugar, por ejemplo:
viewModel.foundDevices.observe(this, Observer { binding.recyclerViewDevices.adapter.updateList(it) })y solo (y siempre) copia la lista si es la misma que está cargada actualmente.
con Kotlin solo necesita convertir su lista a una nueva MutableList como esta u otro tipo de lista según su uso
.observe(this, Observer { adapter.submitList(it?.toMutableList()) })Para mí, este problema apareció si estaba usando RecyclerView dentro de ScrollView con nestedScrollingEnabled="false" y la altura de RV establecida en wrap_content .
El adaptador se actualizó correctamente y se llamó a la función de vinculación, pero no se mostraron los elementos: el RecyclerView estaba atascado en su tamaño original.
Cambiar ScrollView a NestedScrollView solucionó el problema.
Para cualquiera que tenga el mismo escenario que el mío, dejo mi solución, que no sé por qué funciona, aquí.
La solución que funcionó para mí fue de @Mina Samir, que envía la lista como una lista mutable.
Mi escenario de problema:
-Cargar una lista de amigos dentro de un fragmento.
ActivityMain adjunta FragmentFriendList (observa los datos en vivo de los elementos de la base de datos de amigos) y, al mismo tiempo, solicita una solicitud http al servidor para obtener toda mi lista de amigos.
Actualice o inserte los elementos del servidor http.
Cada cambio enciende la devolución de llamada onChanged de livedata. Pero, cuando es la primera vez que inicio la aplicación, lo que significa que no había nada en mi mesa, la lista de envío se realiza correctamente sin ningún tipo de error, pero no aparece nada en la pantalla.
Sin embargo, cuando es la segunda vez que inicio la aplicación, los datos se cargan en la pantalla.
La solución es, como se mencionó anteriormente, enviar la lista como mutableList.
El uso de la primera respuesta de @RJFares actualiza la lista correctamente, pero no mantiene el estado de desplazamiento. Todo el RecyclerView comienza desde la posición 0. Como solución, esto es lo que hice:
fun updateDataList(newList:List<String>){ //new list from DB or Network val tempList = dataList.toMutableList() // dataList is the old list tempList.addAll(newList) listAdapter.submitList(tempList) // Recyclerview Adapter Instance dataList = tempList } De esta forma, puedo mantener el estado de desplazamiento de RecyclerView junto con los datos modificados.
En mi caso, olvidé configurar LayoutManager para RecyclerView . El efecto de eso es el mismo que el descrito anteriormente.
Necesitaba modificar mis DiffUtils
override fun areContentsTheSame(oldItem: Vehicle, newItem: Vehicle): Boolean {
Para devolver realmente si los contenidos son nuevos, no solo compare la identificación del modelo.
Tuve un problema similar. El problema estaba en las funciones Diff , que no comparaban adecuadamente los elementos. Cualquiera que tenga este problema, asegúrese de que sus funciones Diff (y, por extensión, sus clases de objetos de datos) contengan definiciones de comparación adecuadas, es decir, comparar todos los campos que podrían actualizarse en el nuevo elemento. Por ejemplo en la publicación original.
override fun areContentsTheSame(oldItem: Artist?, newItem: Artist?): Boolean { return oldItem == newItem } Esta función (potencialmente) no hace lo que dice en la etiqueta: no compara el contenido de los dos elementos, a menos que haya anulado la función equals() en la clase Artist . En mi caso no lo tenía, y la definición de areContentsTheSame solo marcaba uno de los campos necesarios, por mi descuido al implementarlo. Esto es igualdad estructural vs. igualdad referencial, puedes encontrar más sobre esto aquí
También me encontré con un problema similar, mi caso de uso era que tenía un controlador de clics y el elemento se seleccionará/no seleccionará (alternar al hacer clic).
Probé la mayor parte del enfoque de las respuestas anteriores, lo único que funcionó es
adapter.submitList(null) adapter.submitList(modifiedList)pero el problema con esto es que cada vez que hago clic en cualquier controlador de clics, la lista completa se vuelve a dibujar, lo cual es muy ineficiente.
Lo que hice ?
Hice datos en vivo que almacenarán el último elemento en el que se hizo clic y observando esos datos en vivo, podemos decirle al adaptador que los datos en vivo se han actualizado como se muestra a continuación
viewModel.lastClicked.observe(viewLifeCycleOwner, { adapter.notifyItemChanged(it) }esto funcionará ... lo que sucede es que cuando obtiene la lista actual, está apuntando a la misma lista en la misma ubicación
Una vez que haya modificado la lista de matrices, debe informar al adaptador qué posición debe cambiarse
este código a continuación está funcionando en mi caso, deseo que pueda ayudar
private fun addItem() { val index = myArrayList.size val position = myArrayList.size+1 myArrayList.add( index, MyArrayClass("1", "Item Name") ) myAdapter.notifyItemInserted(position) // in case of insert // in case of remove item // val index = myArrayList.size-1 // myAdapter.notifyItemRemoved(index) }Tengo un comportamiento extraño. Estoy usando MutableList en LiveDate.
En kotlin, los siguientes códigos no funcionan:
mViewModel.products.observe(viewLifecycleOwner, { mAdapter.submitList(it) })Pero, cuando lo cambio a it.toList(), funciona
mViewModel.products.observe(viewLifecycleOwner, { mAdapter.submitList(it.toList()) })Aunque, "eso" era la misma lista.
Tuve un problema MUY similar a este y decidí abrir un nuevo hilo e incluso crear un proyecto de GitHub para jugar. La mayoría de las soluciones no funcionaron para mí, ni siquiera la forma toMutableList() . En mi caso, el problema se resolvió usando clases inmutables y enviando List inmutables al Adaptador.
En realidad, confundió las funciones de anulación en su clase ArtistsDiff
En lugar de esto
class ArtistsDiff : DiffUtil.ItemCallback<Artist>() { override fun areItemsTheSame(oldItem: Artist?, newItem: Artist?): Boolean { return oldItem?.artistId == newItem?.artistId } override fun areContentsTheSame(oldItem: Artist?, newItem: Artist?): Boolean { return oldItem == newItem } }Utilizar esta
class ArtistsDiff : DiffUtil.ItemCallback<Artist>() { override fun areItemsTheSame(oldItem: Artist?, newItem: Artist?): Boolean { return oldItem == newItem // remove artistId } override fun areContentsTheSame(oldItem: Artist?, newItem: Artist?): Boolean { return oldItem?.artistId == newItem?.artistId // add artistId } }Consulte también esta publicación para obtener más información sobre cómo usar ListAdapter.
Perdió tanto tiempo para resolver el problema en el mismo caso.
Pero en mi situación, el problema fue que olvidé especificar un layoutManager para mi recyclerView: vRecyclerView.layoutManager = LinearLayoutManager(requireContext())
Espero que nadie repita mi error...
La razón por la que no se llama a su ListAdapter .submitlist es porque el objeto que actualizó aún tiene la misma dirección en la memoria.
Cuando actualiza un objeto con digamos .setText, cambia el valor en el objeto original.
De modo que cuando verifique si object.id == object2.id, volverá igual porque ambos tienen una referencia a la misma ubicación en la memoria.
La solución es crear un nuevo objeto con los datos actualizados e insertarlo en su lista. Luego se llamará a submitList y funcionará correctamente.
Solución óptima: para Kotlin
var list :ArrayList<BaseModel> = ArrayList(adapter.currentList) list.add(Item("Content")) adapter.submitList(list) { Log.e("ListAdaptor","List Updated Successfully") }No debemos mantener otra lista base, ya que adapter.currentList devolverá una lista en la que ya se calculó la diferencia.
Tenemos que proporcionar una nueva instancia cada vez que se actualiza una lista debido a DiffUtil. Según la documentación de Android, DiffUtil es una clase de utilidad que calcula la diferencia entre dos listas y genera una lista de operaciones de actualización que convierte la primera lista en la segunda. AsyncListDiffer ya mantiene una lista que ejecuta el diffutil en el subproceso de fondo y otra debe pasarse usando adaptor.submitList()
Resuelve mi problema. Creo que la mejor manera es no anular la lista de submitList sino agregar una nueva función para agregar una nueva lista.
fun updateList(list: MutableList<ScaleDispBlock>?) { list?.let { val newList = ArrayList<ScaleDispBlock>(list) submitList(newList) } }Como ya se mencionó, no puede enviar una Lista con la misma referencia porque ListAdapter verá que las listas están en la misma ubicación y, por lo tanto, no podrá usar DiffUtil.
La solución más sencilla sería hacer una copia superficial de la lista.
submitList(ArrayList(list))Tenga cuidado al convertir la lista en MutableList, ya que eso puede crear condiciones para excepciones y errores difíciles de encontrar.
La forma en que funcionó para mí es anular la lista de submitList() y crear una copia de la lista entrante y cada elemento dentro de ella también:
override fun submitList(list: List<Item>?) { val listCopy = mutableListOf<Item>().apply { list?.map { add(Item(it.id, it.name, it.imageUrl)) } } super.submitList(listCopy) }Me encuentro con un problema muy similar.
Después de cambiar la lista de datos, la envío de nuevo, la vista del reciclador no se muestra como yo quería. Muestra elementos duplicados.
No he encontrado la causa raíz, pero encuentro una solución alternativa, que es configurar el adaptador en la vista de reciclador nuevamente. Supongo que esto hace que el visor del reciclador olvide la memoria anterior y vuelva a renderizar correctamente.
userNftListFiltered = SOME_NEW_VALUE binding.nftSendSearchList.adapter = searchNftAdapter //set adapter again searchNftAdapter.submitList(userNftListFiltered)Tuve un problema similar, pero la representación incorrecta se debió a una combinación de setHasFixedSize(true) y android:layout_height="wrap_content" . Por primera vez, el adaptador se suministró con una lista vacía, por lo que la altura nunca se actualizó y era 0 . De todos modos, esto resolvió mi problema. Alguien más podría tener el mismo problema y pensará que es un problema con el adaptador.
Según los documentos oficiales:
Cada vez que llama a submitList , envía una nueva lista para que se diferencie y se muestre.
Esta es la razón por la que cada vez que llama a submitList en la lista anterior (ya enviada), no calcula la diferencia y no notifica al adaptador para cambios en el conjunto de datos.
Editar: entiendo por qué sucede esto, ese no era mi punto. Mi punto es que al menos necesita dar una advertencia o llamar a la función notifyDataSetChanged() . Porque aparentemente estoy llamando a la submitList(...) por una razón. Estoy bastante seguro de que la gente está tratando de averiguar qué salió mal durante horas hasta que se dan cuenta de que la lista de envío () ignora la llamada en silencio.
Esto se debe a la extraña lógica de Google . Entonces, si pasa la misma lista al adaptador, ni siquiera llama a DiffUtil .
public void submitList(final List<T> newList) { if (newList == mList) { // nothing to do return; } .... } Realmente no entiendo todo el punto de este ListAdapter si no puede manejar cambios en la misma lista. Si desea cambiar los elementos en la lista, pase a ListAdapter y vea los cambios, entonces necesita crear una copia profunda de la lista o necesita usar RecyclerView normal con su propia clase DiffUtill .
La biblioteca asume que está utilizando Room o cualquier otro ORM que ofrece una nueva lista asíncrona cada vez que se actualiza, por lo que simplemente llamar a submitList funcionará, y para los desarrolladores descuidados, evita hacer los cálculos dos veces si se llama a la misma lista.
La respuesta aceptada es correcta, ofrece la explicación pero no la solución.
Lo que puede hacer en caso de que no esté utilizando ninguna de esas bibliotecas es:
submitList(null); submitList(myList);Otra solución sería anular la lista de envío (que no causa ese parpadeo rápido) como tal:
@Override public void submitList(final List<Author> list) { super.submitList(list != null ? new ArrayList<>(list) : null); }O con código Kotlin:
override fun submitList(list: List<CatItem>?) { super.submitList(list?.let { ArrayList(it) }) }Lógica cuestionable pero funciona perfectamente. Mi método preferido es el segundo porque no hace que cada fila reciba una llamada onBind.