152 votes

ListAdapter ne met pas à jour l'élément dans RecyclerView

J'utilise la nouvelle bibliothèque de support ListAdapter . Voici mon code pour l'adaptateur

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)
        }
    }
}

Je mets à jour l'adaptateur avec

musicViewModel.getAllArtists().observe(this, Observer {
            it?.let {
                artistAdapter.submitList(it)
            }
        })

Ma classe de différence

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
    }
}

Ce qui se passe, c'est que lorsque submitList est appelé la première fois, l'adaptateur rend tous les éléments, mais lorsque submitList est appelé à nouveau avec des propriétés d'objet mises à jour, il ne rend pas à nouveau la vue qui a changé.

Il redéfinit la vue lorsque je fais défiler la liste, ce qui appelle à son tour bindView()

De plus, j'ai remarqué qu'appeler adapter.notifyDatasSetChanged() après la soumission de la liste rend la vue avec les valeurs mises à jour, mais je ne veux pas appeler notifyDataSetChanged() parce que l'adaptateur de liste a des utilitaires de diff intégrés

Quelqu'un peut-il m'aider ?

185voto

insa_c Points 1241

Edit : Je comprends pourquoi cela arrive, ce n'était pas mon but. Ce que je veux dire, c'est qu'il faut au moins donner un avertissement ou appeler le système de gestion de la sécurité. notifyDataSetChanged() fonction. Parce qu'apparemment, j'appelle la fonction submitList(...) pour une raison précise. Je suis sûr que les gens essaient de comprendre ce qui a mal tourné pendant des heures jusqu'à ce qu'ils comprennent que submitList() ignore silencieusement l'appel.

Cela s'explique par Google La logique bizarre de l'entreprise. Ainsi, si vous passez la même liste à l'adaptateur, il n'appelle même pas la fonction DiffUtil .

public void submitList(final List<T> newList) {
    if (newList == mList) {
        // nothing to do
        return;
    }
....
}

Je ne comprends vraiment pas l'intérêt de tout ça. ListAdapter s'il ne peut pas gérer les changements sur la même liste. Si vous voulez changer les éléments de la liste, vous passez à l'option ListAdapter et voir les changements, alors soit vous devez créer une copie profonde de la liste, soit vous devez utiliser l'outil régulier de gestion des listes. RecyclerView avec votre propre DiffUtill classe.

111voto

RJFares Points 650

La bibliothèque suppose que vous utilisez Room ou tout autre ORM qui propose une nouvelle liste asynchrone à chaque mise à jour, de sorte qu'il suffit d'appeler submitList sur cette liste pour que cela fonctionne, et pour les développeurs négligents, cela évite de faire les calculs deux fois si la même liste est appelée.

La réponse acceptée est correcte, elle offre l'explication mais pas la solution.

Ce que vous pouvez faire si vous n'utilisez pas de telles bibliothèques :

submitList(null);
submitList(myList);

Une autre solution serait de remplacer submitList (qui ne provoque pas ce clignotement rapide) comme tel :

@Override
public void submitList(final List<Author> list) {
    super.submitList(list != null ? new ArrayList<>(list) : null);
}

C'est une logique un peu débile mais qui fonctionne parfaitement. Ma méthode préférée est la deuxième parce qu'elle n'entraîne pas d'appel onBind pour chaque ligne.

42voto

Meno Points 11

avec Kotlin, il suffit de convertir votre liste en une nouvelle Liste mutable comme ceci ou un autre type de liste selon votre usage

.observe(this, Observer {
            adapter.submitList(it?.toMutableList())
        })

26voto

Jan Veselý Points 116

J'ai eu un problème similaire, mais le rendu incorrect était dû à une combinaison des éléments suivants setHasFixedSize(true) et android:layout_height="wrap_content" . Pour la première fois, l'adaptateur a été fourni avec une liste vide, de sorte que la hauteur n'a jamais été mise à jour et était 0 . Quoi qu'il en soit, cela a résolu mon problème. Quelqu'un d'autre pourrait avoir le même problème et penser que c'est un problème dans l'adaptateur.

7voto

Bojan P. Points 481

Aujourd'hui, je suis également tombé sur ce "problème". Avec l'aide de Réponse d'insa_c et La solution de RJFares Je me suis fait une fonction d'extension 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)
}

qui peut ensuite être utilisé partout, par exemple :

viewModel.foundDevices.observe(this, Observer {
    binding.recyclerViewDevices.adapter.updateList(it)
})

et il ne copie (et toujours) la liste que si elle est la même que celle actuellement chargée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X