8
votes

ListAdapter ne rafraîchit pas RecyclerView si je soumets la même liste avec un ordre d'article différent

J'utilise un RecyclerView avec ListAdapter (qui utilise AsyncListDiffer a > pour calculer et animer les changements lorsque la liste est remplacée).

Le problème est que si je soumet () une liste, puis réorganise cette liste et submit () code> encore une fois, rien ne se passe.

Comment forcer ListAdapter à évaluer cette nouvelle liste, même si elle est "la même" (mais l'ordre a changé)? p >

Nouveaux résultats:

J'ai vérifié le code source de submitList () et au début il y a une vérification:

public void submitList(@Nullable final List<T> newList) {
        final int runGeneration = ++this.mMaxScheduledGeneration;
        if (newList != this.mList) {

Je pense que c'est le problème. Mais comment le dépasser? Je veux dire, les développeurs ont sûrement pensé à soumettre une liste ordonnée différente?


9 commentaires

@MDNaseemAshraf mais cela irait à l'encontre de l'objectif général de l'utilisation de ListAdapter. ListAdapter distribue automatiquement les modifications minimales requises à RecyclerView.


En effet. Avez-vous essayé notifyItemRangeChanged (0, List.size ()); ? Ouais, je viens de réaliser que cela aussi irait à l'encontre de l'objectif du changement minimal.


@MDNaseemAshraf connaissez-vous ListAdapter ? Parce que je lis que nous n'avons pas besoin d'utiliser de fonctions notify___ avec elle car elle gère tout automatiquement.


évidemment sans copie de l'ancienne liste vous n'avez rien à comparer


@Selvin, je ne suis pas sûr de comprendre. ListAdapter enregistre l'ancienne liste et lorsque vous appelez submit (newList) , il effectue une comparaison et informe RecyclerView quels éléments ont changé.


Puisque vous avez changé l'ordre de l'ensemble de données, ListAdapter le considérera-t-il comme un nouvel ensemble.


@ PrimožKralj enregistre (fait une copie) ou garde simplement les références?


@Selvin, bon point. Voilà le problème.


Veuillez vérifier stackoverflow.com/a/53123659/6932487 au lieu de soumettre une liste nulle ou nouvelle (et perdre toute la signification du diffutils) vous devez utiliser le callback dataobserver


5 Réponses :


1
votes

Cette fonction ne sera pas appelée car ListAdapter ne la considérera pas comme une autre liste, car elle contient tous les mêmes éléments , seule la commande a été modifiée.

submitList(null);
submitList(orderChangedList);

Donc, pour résoudre ce problème, vous devez d'abord appeler cette fonction avec null , puis appeler immédiatement avec la liste de modification de l'ordre.

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


4 commentaires

Oui, cela aide! Je suppose qu'il a été conçu de cette façon.


Ceci est incorrect, et pas non plus comment submitList est censé être utilisé. Le problème réel est que submitList nécessite une nouvelle liste à soumettre, pas une référence à la même liste qui a été modifiée. Sinon, lorsque vous modifiez la liste, vous modifiez également la liste stockée par le ListAdapter et lorsque vous le soumettez, il compare la liste entrante (la même liste) et conclut que rien n'a changé. Consultez ce lien pour faire une copie de votre liste en Java ou utilisez .toList () < / code> à Kotlin


@Carson Holzheimer pouvez-vous s'il vous plaît élaborer sur cela et poster comme réponse


Cela fonctionne très bien!



1
votes

Il élimine les ListAdapter but pour calculer et animer automatiquement les changements de liste lorsque vous appelez ces lignes consécutivement:

public void submitList(@Nullable List<T> list) {
    mDiffer.submitList(list != null ? new ArrayList<>(list) : null);
}

Cela signifie que vous avez uniquement effacé ( null ) la liste actuelle de ListAdapter, puis soumis ( .submitList () ) une nouvelle liste. Ainsi, aucune animation correspondante ne sera vue dans ce cas mais seulement une actualisation de l'ensemble de RecyclerView.

La solution consiste à implémenter la méthode .submitList (List list) à l'intérieur votre ListAdapter comme suit:

submitList(null);
submitList(orderChangedList);

De cette façon, vous autorisez le ListAdapter à conserver sa currentList et à le faire "différer" avec le newList, donc les animations calculées, par opposition à "diffing "avec un null.

Remarque: Cependant, aucune animation de mise à jour ne se produira, si, bien sûr, la newList contient les mêmes éléments dans le même commander comme liste d'origine.


4 commentaires

Cela n'aura aucun effet si les deux listes soumises ultérieurement contiennent les mêmes éléments dans un ordre différent (c'est le problème central, comme décrit dans la question initiale).


même si la liste vient d'être réorganisée, tant que vous instanciez une nouvelle arrayList en utilisant new ArrayList <> (YourReOrderedList) dans votre ListAdapter, la différence prendra effet à moins que le processus de réorganisation ne se termine avec le même ordre que votre OriginalList.


En fait, vous avez raison. Mais deux problèmes avec cette approche sont que (pour une raison inconnue) il faut 2-3 secondes avant qu'une animation ne soit vue ET, la position de la vue de recyclage est laissée intacte (je veux qu'elle défile vers le haut après le "tri", voir ceci lié < une href = "https://stackoverflow.com/questions/55262409/recyclerview-scroll-to-top-with-asynclistdiffer-not-working"> ma question ).


La meilleure façon de revenir en haut est ici: stackoverflow.com/a/53123659/4672107



12
votes

Au lieu de

newList.first().isFavourite = false

Vous devez soumettre une nouvelle liste comme celle-ci:

val newList = oldList.toList() // Unintuitive way to copy a list
newList.first() = newList.first().copy(isFavourite = false) // Do whatever modifications you want
submitList(newList)

C'est une sorte de problème avec l'API. Nous nous attendrions à ce que ListAdapter conserve une copie de la liste, mais ce n'est pas le cas, probablement pour des raisons de mémoire. Lorsque vous modifiez votre ancienne liste, vous modifiez en fait la même liste que ListAdapter a stockée. Lorsque ListAdapter vérifie if (newList! = This.mList) newList et mList font référence à la même instance de liste, donc peu importe ce que vous avez changé dans cette liste, elle s'égalisera et ignorera votre mise à jour. p >

Dans kotlin, vous pouvez créer une nouvelle liste via:

ArrayList newList = new ArrayList(oldList);
newList.add(somethingNew); // Or sort or do whatever you want
submitList(newList);

Notez que vous ne pouvez pas faire ceci:

submitList(mySameOldListThatIModified)

parce que cela changera également le premier élément de votre ancienne liste, et encore une fois ListAdapter ne verra pas de différence entre le premier élément de votre ancienne liste et le premier élément de votre nouvelle liste. Je recommanderais que tous les éléments de votre liste aient exclusivement des propriétés val , pour éviter ce problème.


0 commentaires

0
votes

Pour ajouter à la réponse de Carson, qui est une meilleure façon de le faire, vous pouvez conserver les avantages de submitList comme suit dans Kotlin:

submitList(oldList.toList().toMutableList().let {
     it[index] = it[index].copy(property = newvalue) // To update a property on an item
     it.add(newItem) // To add a new item
     it.removeAt[index] // To remove an item
     // and so on....
     it
})


0 commentaires

0
votes

Appelez simplement listAdapter.notifyDataSetChanged () et le ListAdapter redessine la liste en fonction des valeurs soumises.


1 commentaires

L'intérêt de l'utilisation d'AsyncListDiffer est de ne pas appeler notifyDataSetChanged inutilement.