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 cuando se llama a submitList la primera vez que el adaptador procesa todos los elementos, pero cuando se llama de nuevo a submitList con las propiedades del objeto actualizadas, no se vuelve a representar la vista que ha cambiado.
Vuelve a renderizar la vista a medida que me desplazo por la lista, que a su vez llama bindView()
Además, he notado que llamar adapter.notifyDatasSetChanged()
después de enviar la lista muestra la vista con valores actualizados, pero no quiero llamar notifyDataSetChanged()
porque el adaptador de lista tiene utilidades de diferencia incorporadas
¿Me puede ayudar alguien?
fuente
ArtistsDiff
y, por tanto, con suArtist
propia implementación .Respuestas:
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
notifyDataSetChanged()
función. Porque aparentemente estoy llamando a lasubmitList(...)
función por una razón. Estoy bastante seguro de que la gente está tratando de averiguar qué salió mal durante horas hasta que descubren que submitList () ignora en silencio la llamada.Esto se debe a
Google
una lógica extraña. Entonces, si pasa la misma lista al adaptador, ni siquiera llama alDiffUtil
.public void submitList(final List<T> newList) { if (newList == mList) { // nothing to do return; } .... }
Realmente no entiendo el punto de esto
ListAdapter
si no puede manejar cambios en la misma lista. Si desea cambiar los elementos de la lista, pasa a laListAdapter
y ve los cambios, entonces debe crear una copia profunda de la lista o debe usar regularRecyclerView
con su propiaDiffUtill
clase.fuente
submitList
, ¿verdad? Al menos debería llamar al ennotifyDataSetChanged()
lugar de ignorar silenciosamente la llamada. Estoy bastante seguro de que la gente está tratando de averiguar qué salió mal durante horas hasta que se dan cuenta de quesubmitList()
ignora en silencio la llamada.RecyclerView.Adapter<VH>
ynotifyDataSetChanged()
. La vida es buena ahora. Buena cantidad de horasnotifyDataSetChanged()
es caro y anularía por completo el punto de tener una implementación basada en DiffUtil. Puede ser cuidadoso e intencionado al llamarsubmitList
solo con datos nuevos, pero en realidad eso es solo una trampa de rendimiento.La biblioteca asume que está usando 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é usando ninguna de estas bibliotecas es:
submitList(null); submitList(myList);
Otra solución sería anular submitList (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 el código de 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 obtenga una llamada onBind.
fuente
.submitList(new ArrayList(list))
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()) })
fuente
Tuve un problema similar, pero la representación incorrecta fue causada por una combinación de
setHasFixedSize(true)
yandroid: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 fue0
. De todos modos, esto resolvió mi problema. Alguien más puede tener el mismo problema y pensar que es un problema en el adaptador.fuente
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)
fuente
Hoy también me topé con este "problema". Con la ayuda de la respuesta de insa_c y la solución de RJFares, 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 * /programming/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 la cargada actualmente.
fuente
Según los documentos oficiales :
Siempre 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 el cambio en el conjunto de datos.
fuente
Para mí, este problema apareció si estaba usando
RecyclerView
dentro deScrollView
connestedScrollingEnabled="false"
y la altura de RV configurada enwrap_content
.El adaptador se actualizó correctamente y se llamó a la función de vinculación, pero los elementos no se mostraron;
RecyclerView
se atascó en su tamaño original.Cambiar
ScrollView
paraNestedScrollView
solucionar el problema.fuente
En mi caso, olvidé configurar el
LayoutManager
paraRecyclerView
. El efecto de eso es el mismo que el descrito anteriormente.fuente
Para cualquiera que tenga el mismo escenario que el mío, dejo mi solución, que no sé por qué está funcionando, 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 los datos en vivo. Pero, cuando es la primera vez que abro la aplicación, lo que significa que no había nada en mi mesa, el submitList tiene éxito sin ningún error de ningún tipo, pero no aparece nada en la pantalla.
Sin embargo, cuando es la segunda vez que abro la aplicación, los datos se cargan en la pantalla.
La solución es, como se mencionó anteriormente, enviar la lista como mutableList.
fuente
Tuve un problema similar. El problema estaba en las
Diff
funciones, que no comparaban adecuadamente los elementos. Cualquiera que tenga este problema, asegúrese de que susDiff
funciones (y por extensión sus clases de objeto 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 originaloverride 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
equals()
función en laArtist
clase. En mi caso no lo había hecho, y la definición deareContentsTheSame
solo marcó uno de los campos necesarios, debido a mi descuido al implementarlo. Esto es igualdad estructural versus igualdad referencial, puedes encontrar más sobre esto aquí.fuente
Necesitaba modificar mis DiffUtils
override fun areContentsTheSame(oldItem: Vehicle, newItem: Vehicle): Boolean {
Para devolver realmente si el contenido es nuevo, no solo compare la identificación del modelo.
fuente
El uso de la primera respuesta de @RJFares actualiza la lista correctamente, pero no mantiene el estado de desplazamiento. Todo
RecyclerView
comienza desde la posición 0. Como solución alternativa, 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 manera, puedo mantener el estado de desplazamiento
RecyclerView
junto con los datos modificados.fuente