6
votes

DiffUtil ItemCallback areContentsTheSame () renvoie toujours true après la mise à jour d'un élément sur le ListAdapter

Lors de l'utilisation d'un ListAdapter, j'ai remarqué qu'après la mise à jour d'un élément, la méthode DiffUtil.ItemCallback areContentsTheSame () retournait toujours true. Débogage du code J'ai réalisé que l'ancien et le nouvel élément étaient exactement les mêmes (l'ancien état a disparu). Par conséquent, la méthode ListAdapter onBindViewHolder () n'était pas appelée pour mettre à jour la ligne respective de la liste (seul l'ordre des éléments a été modifié avec une belle animation).

Vérifier d'autres questions sur Stackoverflow semble être un problème courant auquel de nombreux développeurs ont été confrontés:

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

ListAdapter ne se met pas à jour lors de la modification du contenu

DiffUtil.Callback ne fonctionne pas comme prévu

Les éléments de Recyclerview + Listadapter ne seront pas redessinés lors de la mise à jour

ListAdapter avec DiffUtil.ItemCallback considère toujours les objets de la même manière

Lors de la soumission d'une nouvelle liste à RecyclerView ListAdapter, la vérification des différences renvoie toujours true pour areContentsTheSame ()

Cependant, aucune des réponses ci-dessus (le cas échéant) n'a fourni une solution correcte.

La seule façon dont cela fonctionnait pour moi était d'appeler notifyDataSetChanged () sur le ListAdapter chaque fois que l'observateur émettait un nouveau résultat.

Mais quel est l'intérêt d'utiliser un ListAdapter et submitList () pour notifier les modifications si toutes les performances exceptionnelles que vous pourriez obtenir sont annulées en forçant le RecyclerView à redessiner son contenu (tous les éléments qu'il a montré jusqu'à présent)?

  1. notifyDataSetChanged() :

Même si cela fonctionne, ce n'est certainement pas la bonne approche si vous avez décidé d'utiliser un ListAdapter en premier lieu.

  1. viewModel.getObjects().observe(this, listOfObjects -> listAdapter.submitList(new ArrayList<>(listOfObjects))); :

Ça n'a pas marché.

Après avoir mis à jour un élément de la liste, j'aimerais voir les modifications respectives se produire sur l'interface utilisateur (pas seulement l'ordre de tri) pour la ligne correspondante comme prévu.


3 commentaires

même problème ici, avez-vous trouvé une solution?


@Nux malheureusement pas encore.


@fmiralles comment ça va? Face au même problème ((


4 Réponses :


0
votes

J'ai passé beaucoup de temps jusqu'à ce que je découvre que j'avais le même problème que celui décrit dans cet article. J'ai essayé de nombreuses solutions, mais au final j'ai réalisé que je gâchais sans le savoir. Si vous rencontrez le même problème, vérifiez si vous ne mettez pas à jour l'interface utilisateur d'abord et appelez une commande d'écriture pour mettre à jour la valeur sur votre référentiel. Dans mon cas, je mettais à jour l'objet LiveData, puis je mettais à jour cette valeur sur Firebase Database. Le problème était dû au fait que mon interface utilisateur écoutait les changements sur cet objet LiveData, donc mon observateur de base de données Firebase a détecté des changements, le ListAdapter a été appelé et il semblait que rien n'avait été changé.

Pour résoudre le problème, j'ai supprimé les lignes qui mettaient à jour l'objet LiveData et j'ai seulement appelé Firebase Database en envoyant les modifications.


0 commentaires

0
votes

Je ne sais pas si c'est la solution pour ce cas spécifique, mais d'après la description, cela ressemble exactement à quelque chose que j'ai vécu très récemment.

Je travaille avec une nouvelle intégration Room + LiveData + ViewModel + DiffUtils pour la première fois et j'avais le même problème avec la mise à jour des éléments dans un RecyclerView après la mise à jour de la liste.

Le problème que j'avais était dû à ma compréhension de la mise à jour d'une liste et de la possibilité pour DiffUtils de faire son travail comme il se doit. Espérons que ce qui suit sera assez clair:

Ce que je faisais:

  1. L'utilisateur met à jour son élément à l'aide d'une boîte de dialogue
  2. L'élément de liste dans RecyclerView est mis à jour avec les nouvelles informations
  3. Room déclenche l'observateur LiveData car quelque chose a peut-être changé
  4. Le RecyclerView DiffUtils essaie de vérifier les différences entre l'ancienne liste d'adaptateurs et la nouvelle
  5. Rien de nouveau n'a été détecté
  6. L'adaptateur n'est pas déclenché pour mettre à jour son interface utilisateur

Mon erreur a été de penser que le problème était en .5, ce qui m'a amené à passer une demi-journée à faire des allers-retours pour déboguer le problème. Finalement, je suis tombé sur une question SO (je ne peux pas la trouver pour le moment) qui m'a conduit à la source correcte du problème. Ce problème se trouve vraiment dans .2 - mise à jour de l'élément dans la liste.

C'est le problème car nous mettons à jour notre liste d'adaptateurs ACTUELS avec les nouvelles modifications avant même que DiffUtils n'ait une chance de comparer les modifications de l'ancienne et de la nouvelle liste. Cela signifie qu'à chaque fois que DiffUtils comparait toujours les listes avec l'ancienne liste contenant déjà les modifications apportées par la nouvelle liste.

La solution? Ne mettez pas à jour l'élément de liste car il s'agit d'un objet et les objets de liste conservent la même référence, ce qui entraîne la mise à jour de la même instance partout où elle est utilisée / référencée. Au lieu de cela, clonez l'objet d'élément (comme dans le clone profond, je sais que cela peut être ennuyeux), appliquez les modifications apportées par l'utilisateur à cet objet cloné, puis utilisez ce clone pour mettre à jour l'entrée d'élément dans la base de données de la salle. NE remplacez PAS l'élément de la liste des adaptateurs par le clone, laissez l'original seul, nous voulons uniquement mettre à jour les informations de la base de données, pas la liste car DiffUtils s'en chargera.

Ce que nous faisons essentiellement ici est de créer une charge utile de mise à jour pour notre base de données de salle, qui déclenchera alors l'observateur LiveData pour comparer l'ancienne liste avec la nouvelle (contenant les données d'élément mises à jour), ce qui entraînera la détection de changement attendu entre les deux listes.


En bref;

Faites ceci:

  • Élément de liste d'adaptateur de clone profond
  • Mettre à jour uniquement le clone avec les nouvelles informations
  • Mettre à jour la base de données avec les informations de clonage

Ne fais pas ça:

  • Mettre à jour directement l'élément de liste d'adaptateurs
  • Mettre à jour la base de données avec les informations d'élément de liste


0 commentaires

0
votes

Je n'ai aucune réputation, donc je ne peux pas commenter, mais la réponse de @ Shadow a fonctionné pour moi.

Je travaille également avec Room + LiveData + ViewModel + DiffUtil et j'édite le contenu d'un élément de liste avec une boîte de dialogue. Bien que java soit passé par valeur, lorsque nous transmettons la valeur d'un objet, nous lui passions en fait la référence. Ainsi, lorsque vous modifiez le contenu du même objet dans une boîte de dialogue, par exemple, vous modifiez en fait la même référence de l'élément de liste, d'où la raison pour laquelle DiffUtil ne peut pas calculer la différence lorsque LiveData effectue un onChange.

Ceci explique mieux Java pass by value / reference: Java est-il "pass-by-reference" ou "pass-by-value"?

Ce que j'ai fait:

Item newItem = new ItemBuilder (). SetId (oldItem.getId ()) .......

Apportez ensuite des modifications à newItem, puis mettez à jour la base de données à partir de viewModel


0 commentaires

0
votes

Je suis également confronté à ce problème récemment. Pour les modifications partielles de ViewHolder, mon approche est la suivante:

  • lorsqu'un clic est détecté dans l'élément de liste de l'adaptateur -> déléguez-le à votre vue via un rappel

  • view déléguera ce clic à ViewModel avec la position du clic.

  • ViewModel mettra à jour la propriété d'objet pour cette position. Dans mon cas, j'ai une List<FeedPost> list . Chaque FeedPost a des informations utilisateur, un bouton de connexion, comme count, etc. Donc, si l'utilisateur clique sur le bouton Like, j'incrémenterai sa valeur dans mon ViewModel comme list.get(clickedPos).incrementLikeByOne() .

  • La mise à jour de la liste dans ViewModel va également refléter le changement dans votre liste d'adaptateurs car la liste d'adaptateurs contient essentiellement la même référence d'objet. Lorsque vous effectuez add() ou addAll() vous ne faites pas de copie complète des éléments de la liste. C'est pourquoi DiffUtil ne parvient pas à détecter les modifications partielles du détenteur de vue.

  • Ce que j'ai fait, c'est créer un HashMap<Integer, Object> changeDetailsMap . Cette carte contiendra les données qui ont changé dans sa valeur et la clé peut être la position et utilise cette carte à l'intérieur de areContentsTheSame() pour retourner vrai / faux afin de déclencher des modifications partielles de ViewHolder. La raison pour laquelle j'utilise un objet pour pouvoir transmettre tout ce que je veux (entier, chaîne ou mon propre POJO), mais vous devez prendre soin de le transtyper correctement dans DiffUtil. Assurez-vous que quel que soit l'objet que vous mettez dans ce HashMap, il a un champ int (disons int classType afin que vous puissiez le convertir en classe correcte)

  • Une chose à noter ici est que vous devez l'utiliser avec SingleLiveEvent comme ceci: SingleLiveEvent<HashMap<Integer, String>> changeDetailsMap_SLE car c'est une opération one-shot et non avec MutableLiveData car il est collant dans la nature. J'utilisais fragment donc lorsque le fragment est sorti de la backstack, LiveData ordinaire envoie à nouveau la valeur récente car il devient actif lorsque le fragment sort de backstack. Vous pouvez google SingleLiveEvent si vous n'en avez pas conscience. Vous pouvez l'utiliser pour une opération ponctuelle. Bien que l'un des googleurs dans son article sur le médium ait suggéré une meilleure approche, mais en pratique, je trouve ce SingleLiveEvent facile à utiliser.

  • pousser ce hashmap pour afficher comme: changeDetailsMap_SLE.setValue(changeDetailsMap) . Vous pouvez observer ce SingleLiveEvent comme un LiveData normal et le transmettre à votre adaptateur et l'envoyer à votre DiffUtil. La façon dont je fais cela dans mon adaptateur est comme ça

     public void updateConnectionStatus(Map<String, String>) {
         List<FeedAdapterModel> pseudoNewList = new ArrayList<>(adapterList); //adapterList is the list which you already have in your adapter class
         DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new FeedDiffUtil(adapterList, pseudoNewList, updatedConnectionStatusMap));
         adapterList.clear();
         adapterList.addAll(pseudoNewList);
         diffResult.dispatchUpdatesTo(this);
     }
    
  • N'oubliez pas non plus d'effacer cette carte de hachage lorsque le travail est effectué sur l'adaptateur. Vous ne voulez pas récupérer les anciennes positions pour lesquelles vous avez exécuté DiffUtil plus tôt lorsque vous repoussez ce SingleLiveEvent.

Cette approche fonctionne également lorsque vous comparez des éléments de liste (à l'intérieur de areItemsTheSame() ) en utilisant la méthode equals() et en n'utilisant pas la propriété d'objet comme oldList.get(oldItemPosition).getPostId() == newList.get(newItemPosition).getPostId() . J'ai une liste d'adaptateurs hétérogène dans mon cas, donc tous les éléments de la liste ne sont pas garantis d'avoir un postID, je ne peux donc pas me fier à une comparaison de propriété d'objet car elle peut être nulle.


0 commentaires