Mise à jour : l'un des problèmes est résolu: maintenant updateList
est résolu, le problème était que j'ai défini mAdapter
comme RecyclerView .Adapter
au lieu de MyAdapter
. Mais maintenant, même si je reçois des données, rien n'apparaît dans la liste, elle est vide
-------------------- ORIGINAL POST --------------------
Je souhaite mettre à jour mon RecyclerView
en utilisant DiffUtil
pour éviter les doublons.
J'ai 4 classes: la classe User, la classe Activity
où je définis les données, la classe Adapter
et le code DiffUtil > classe. Je ne suis pas sûr de combiner tous ces 4 correctement.
Voici la classe User:
public class MyFragment extends Fragment { private ArrayList<User> myDataset; private RecyclerView.Adapter mAdapter; public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment rootView = inflater.inflate(R.layout.fragment_lks, container, false); mRecyclerView = (RecyclerView) rootView.findViewById(R.id.my_recycler_view); myDataset = new ArrayList<User>(); mAdapter = new MyAdapter(myDataset);
C'est ainsi que je définit les données dynamiquement (je continue à de nouveaux tableaux Json
du serveur contenant les identifiants utilisateur à afficher, puis j'ai défini l'image utilisateur à partir du stockage Firebase
): (C'est une fonction appelée par un auditeur onClick:)
Ceci est l'appel de méthode depuis le fragment:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> { private ArrayList<User> mDataset; private MyViewHolder myHolder; private User user; public static class MyViewHolder extends RecyclerView.ViewHolder { public TextView singleItemTextView; public ImageView singleItemImage; public View layout; public ConstraintLayout constraintLayout; public MyViewHolder(View v) { super(v); layout = v; singleItemImage = (ImageView) v.findViewById(R.id.icon); singleItemTextView = (TextView) v.findViewById(R.id.singleitemtv); constraintLayout = (ConstraintLayout) v.findViewById(R.id.nbConstraintLayout); } } // Provide a suitable constructor (depends on the kind of dataset) public MyAdapter(ArrayList<User> myDataset) { mDataset = myDataset; } // Create new views (invoked by the layout manager) @Override public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()) .inflate(R.layout.nb_image_view, parent, false); MyViewHolder vh = new MyViewHolder(v); return vh; } @Override public void onBindViewHolder(final MyViewHolder holder, final int position) { myHolder = holder; user = mDataset.get(position); Uri userImage = user.getImageUrl(); myHolder.singleItemTextView.setText(user.getUserId()); Glide.with(myHolder.itemView.getContext() /* context */) .load(userImage) .into(myHolder.singleItemImage); myHolder.constraintLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Context context = v.getContext(); Intent intent = new Intent(v.getContext(), DisplayUserActivity.class); context.startActivity(intent); } }); } public void updateList(ArrayList<User> newList) { DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new MyDiffUtilCallBack(this.mDataset, newList)); diffResult.dispatchUpdatesTo(this); } }
Voici la fonction:
public class MyDiffUtilCallBack extends DiffUtil.Callback{ ArrayList<User> oldUsers; ArrayList<User> newUsers; public MyDiffUtilCallBack(ArrayList<User> newUsers, ArrayList<User> oldUsers) { this.newUsers = newUsers; this.oldUsers = oldUsers; } @Override public int getOldListSize() { return oldUsers.size(); } @Override public int getNewListSize() { return newUsers.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { return oldUsers.get(oldItemPosition).getUserId().equals( newUsers.get(newItemPosition).getUserId()); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { return oldUsers.get(oldItemPosition).equals(newUsers.get(newItemPosition)); } @Nullable @Override public Object getChangePayload(int oldItemPosition, int newItemPosition) { //you can return particular field for changed item. return super.getChangePayload(oldItemPosition, newItemPosition); } }
Voici à quoi ressemble ma classe DiffUtil
:
private void updateUsersList() { @Override public void onResponse(JSONArray response) { // the JSON ARRAY response of user ids ["uid1", "uid334", "uid1123"] myDataset.clear(); // clear dataset to prevent duplicates for (int i = 0; i < response.length(); i++) { try { String userKey = response.get(i).toString(); // the currently iterated user id final DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference(); DatabaseReference userKeyRef = rootRef.child("users").child(userKey); // reference to currently iterated user ValueEventListener listener = new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { myDataset.add(new User(dataSnapshot.getKey(), dataSnapshot.child("imageUrl").getValue().toString())); //add new user: id and image url mAdapter.updateList(myDataset); // cannot resolve this method, why? } @Override public void onCancelled(@NonNull DatabaseError databaseError) { Log.d(TAG, databaseError.getMessage()); } }; userKeyRef.addListenerForSingleValueEvent(listener); } catch (JSONException e) { Log.d(TAG, "message " + e); } } }
Et voici mon adaptateur:
button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { updateUsersList(); } });
Je ne suis pas sûr de combiner correctement toutes les classes (ma première fois en utilisant DiffUtil
), et j'obtiens également impossible de résoudre la méthode updateList (?)
Qu'est-ce que je fais mal?
Voici comment je définis mAdapter dans mon Fragment:
public class User { private String mUserId; private Uri mImageUrl; public User(String userId, String imageUrl) { mUserId = userId; mImageUrl = Uri.parse(imageUrl); } public String getUserId() { return mUserId; } public Uri getImageUrl() { return mImageUrl; } }
4 Réponses :
Le problème vient de la définition de mAdapter
. Vous l'avez défini comme RecyclerView.Adapter
qui est la super classe de votre MyAdapter
et il ne contient pas updateList ()
. Vous devez le modifier comme suit:
public class User { private String mUserId; private Uri mImageUrl; public User(String userId, String imageUrl) { mUserId = userId; mImageUrl = Uri.parse(imageUrl); } public String getUserId() { return mUserId; } public Uri getImageUrl() { return mImageUrl; } @Override public boolean equals(Object other) { if (other instanceof User) { User user = (User) other; return mUserId.equals(user.getUserId()) && mImageUrl.equals(user.getImageUrl()); } else { return false; } } }
Mise à jour 13/01/2019:
J'ai réécrit votre adaptateur avec AsyncListDiffer
qui calcule la différence de manière asynchrone puis l'applique à l'adaptateur.
MyAdapter.java
import android.content.Context; import android.content.Intent; import android.net.Uri; import android.support.annotation.NonNull; import android.support.constraint.ConstraintLayout; import android.support.v7.recyclerview.extensions.AsyncListDiffer; import android.support.v7.util.DiffUtil; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import com.bumptech.glide.Glide; import java.util.List; public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> { private AsyncListDiffer<User> mAsyncListDiffer; public static class MyViewHolder extends RecyclerView.ViewHolder { public TextView singleItemTextView; public ImageView singleItemImage; public View layout; public ConstraintLayout constraintLayout; public MyViewHolder(View v) { super(v); layout = v; singleItemImage = (ImageView) v.findViewById(R.id.icon); singleItemTextView = (TextView) v.findViewById(R.id.singleitemtv); constraintLayout = (ConstraintLayout) v.findViewById(R.id.nbConstraintLayout); } } // Provide a suitable constructor (depends on the kind of dataset) public MyAdapter() { DiffUtil.ItemCallback<User> diffUtilCallback = new DiffUtil.ItemCallback<User>() { @Override public boolean areItemsTheSame(@NonNull User newUser, @NonNull User oldUser) { return newUser.getUserId().equals(oldUser.getUserId()); } @Override public boolean areContentsTheSame(@NonNull User newUser, @NonNull User oldUser) { return newUser.equals(oldUser); } }; mAsyncListDiffer = new AsyncListDiffer<>(this, diffUtilCallback); } // Create new views (invoked by the layout manager) @Override public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.nb_image_view, parent, false); MyViewHolder vh = new MyViewHolder(v); return vh; } @Override public void onBindViewHolder(final MyViewHolder holder, final int position) { User user = mAsyncListDiffer.getCurrentList().get(position); Uri userImage = user.getImageUrl(); holder.singleItemTextView.setText(user.getUserId()); Glide.with(holder.itemView.getContext() /* context */) .load(userImage) .into(holder.singleItemImage); holder.constraintLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Context context = v.getContext(); Intent intent = new Intent(v.getContext(), DisplayUserActivity.class); context.startActivity(intent); } }); } @Override public int getItemCount() { return mAsyncListDiffer.getCurrentList().size(); } public void updateList(List<User> newList) { mAsyncListDiffer.submitList(newList); } }
User.java
private MyAdapter mAdapter;
Merci, maintenant 1 problème est résolu: la updateList
est visible, mais maintenant la liste ne montrera plus rien (rien n'apparaît bien que j'obtienne des données). Lorsque je le teste avec notifyDataSetChanged
, il affiche les données
Vous êtes les bienvenus. Je pense qu'il vaut mieux définir public MyAdapter ()
au lieu de public MyAdapter (ArrayList
et dans MyAdapter
définir et instancier < code> mDataset as private ArrayList
. Cela peut résoudre le problème.
vous voulez dire laisser le public MyAdapter () {}
vide? Et qu'en est-il de l'autre commentaire? À propos de la substitution de la méthode equals
dans ma classe User
? J'ai fait ce que vous avez dit, et il n'affiche toujours rien: (Notez également qu'il y a mDataset
et myDataset
à ne pas confondre avec les deux
Pourriez-vous s'il vous plaît modifier votre réponse avec des correctifs possibles pour que je sache exactement quoi faire? Merci
Oui, je veux dire qu'il ne lui passe pas la liste des utilisateurs. Cela conduit à un mDataset
vide la première fois, puis lorsqu'une liste est extraite de la base de données, DiffUtil
calcule la différence entre une liste vide et une liste récupérée qui conduit à afficher tous les nouvelles données. Bien sûr, je vais le modifier.
Ok, j'ai fait exactement ça, mais rien n'apparaît, il nous manque quelque chose. Quelqu'un dans les commentaires a mentionné quelque chose sur le remplacement de equals
dans la classe User
, ce que je n'ai pas fait, et je ne sais pas comment le faire. Mais je ne sais pas si cela va résoudre le problème. Que nous manque-t-il? updateList
est censé remplacer complètement notifyDataSetChanged
?
Oui, il y a un bogue dans areItemsTheSame
. Lorsque vous avez défini mUserId
comme String
, vous devez vérifier l'égalité en utilisant est égal à
, et non ==
. Nous avons donc: return oldUsers.get (oldItemPosition) .getUserId (). Equals (newUsers.ge t (newItemPosition) .g etUserId ());
J'édite la réponse avec mis à jour Classe utilisateur
. Cela peut résoudre le problème comme vous le pensez.
La updateList
mise à jour n'est pas égale à notifyDataSetChanged
. Parce que le plus gros de la mise à jour de l'ensemble des éléments est de réaffecter toutes les valeurs aux vues. Il remplace simplement les données ArrayList
et en utilisant DiffUtil
il met à jour les seules vues
requises.
En fait, j'ai déjà changé cette partie en est égal à
, mais il a mentionné autre chose, remplaçant égal à areContentsTheSame
. Rien n'apparaît. peut-être que je dois appeler au moins une fois onDataSetChanged
dans un premier temps?
onDataSetChanged
n'est pas du tout nécessaire. Est-il possible de partager plus de code du fragment, ou mieux de partager le projet dans un dépôt github privé et de m'y ajouter.
Le projet est en désordre, car je suis débutant, il n'y a vraiment pas grand chose à ajouter. À propos, lorsque je connecte myDataset
à chaque itération, il affiche les éléments corrects, ce qui signifie que l'ensemble de données n'est pas vide et contient les éléments corrects, le problème se situe donc quelque part dans la classe DiffUtil / Adapter / User
Cela semble fonctionner: les doublons n'apparaîtront plus dans la liste, mais les doublons restent dans l'ensemble de données. Existe-t-il un moyen d'empêcher l'ajout de doublons à l'ensemble de données? Parce que si l'utilisateur appelle la fonction plusieurs fois, il y aura un grand ensemble de données avec des doublons (bien qu'ils ne soient pas affichés à l'écran). Comment comparer les deux listes et les ajouter ensuite à l'ensemble de données? Parce que pour le moment, il ajoute la nouvelle liste à l'ensemble de données, et alors seulement il vérifie les doublons, j'ai donc un ensemble de données avec de nombreux doublons
J'ai une idée! Je mettrai myDataset.clear ()
après la fin de la boucle for
, non?
Oui, vous avez raison, effacez la liste avant d'ajouter de nouveaux éléments récupérés.
Dernier problème maintenant: la liste ne se remplit à l'écran qu'au premier appel, alors elle n'ajoutera pas les éléments nouvellement ajoutés. Exemple: si au début il y a une liste de 4 éléments, alors j'appelle à nouveau la fonction et le serveur a renvoyé un autre élément donc il y en a au total 5, il ne mettra pas à jour la liste à l'écran, bien que l'ensemble de données affiche 5 éléments. La liste est bloquée dans sa configuration initiale (je dois fermer l'application, puis appeler à nouveau la fonction pour afficher les 5 éléments)
Êtes-vous sûr que votre liste de passage à updateList
contient 5 éléments? Vérifiez-le s'il vous plaît avec un point de rupture.
Je suis certain que je passe à chaque fois une liste de tailles différentes, mais elle n'est bloquée que sur la première liste
Une idée de pourquoi il n'affiche que la première liste monsieur? Peut-être que cela a quelque chose à voir avec le fait que je "construis" la liste à chaque itération? (Vous voyez que j'ajoute un utilisateur à chaque pour
itération de boucle)
Je l'ai vérifié à nouveau. Il semble que ça va. Parce que j'ai utilisé exactement ce modèle dans mes codes. Si vous voulez, laissez-moi voir votre code pour en connaître les aspects latents mec.
En plus de la réponse de @ aminography, je vous suggère d'utiliser ListAdapter
, une implémentation de RecyclerView.Adapter
qui facilite la mise à jour de RecyclerView
avec les animations appropriées. Cette classe est incluse dans la bibliothèque de support de recyclerview.
Voici un exemple d'utilisation basé sur votre cas d'utilisation:
public class MyAdapter extends ListAdapter<User, UserViewHolder> { public MyAdapter() { super(new UserDiffCallback()); } public UserViewHolder onCreateViewHolder(int position, int viewType) { ... } public void onBindViewHolder(UserViewModel holder, int position) { User userAtPosition = getItem(position); // getItem is a protected method from ListAdapter // Bind user data to your holder... } } public class UserDiffCallback extends DiffUtil.ItemCallback<User> { @Override public boolean areItemsTheSame(@NonNull User oldUser, @NonNull User newUser) { return oldUser.getUserId().equals(newUser.getUserId()); } @Override public boolean areContentsTheSame(@NonNull User oldUser, @NonNull User newUser) { // No need to check the equality for all User fields ; just check the equality for fields that change the display of your item. // In your case, both impact the display. return oldUser.getUserId().equals(newUser.getUserId()) && (oldUser.getImageUrl() == null) ? newUser.getImageUrl() == null : oldUser.getImageUrl().equals(newUser.getImageUrl()); } }
Ensuite, lorsque vous devez mettre à jour la liste avec nouveaux utilisateurs, appelez myAdapter.submitList (newList)
. Tout comme avec AsyncListDiffer
, la différence entre les deux listes est calculée sur un thread d'arrière-plan.
J'ai remarqué que chaque fois que j'ajoutais plus de liste à mon listadapter en utilisant submitList, la nouvelle liste remplace l'ancienne. Est-ce normal ou est-ce que je fais quelque chose de mal?
Oui, c'est un comportement normal. Lorsque vous appelez submitList
, diff est calculé sur un thread d'arrière-plan puis l'ancienne liste est remplacée par la nouvelle.
Merci pour la réponse, cela signifie que ListAdapter peut réaliser un défilement sans fin car il n'ajoute pas à la liste précédente. Je me suis fait craquer la tête ... J'apprécie vraiment vos commentaires
Si je comprends bien, vous souhaitez implémenter un RecyclerView.Adapter
qui charge de nouveaux éléments lorsque vous faites défiler? C'est toujours possible avec ListAdapter
: fusionnez les éléments de la liste précédente avec les nouveaux, puis appelez submitList
avec la liste fusionnée.
Merci, je n'y ai jamais pensé.
On dirait
public void onBindViewHolder (@NonNull ViewHolder holder, int position, @NonNull List
non implémenté
Implémentez ceci pour utiliser DiffUtils correctement, car cette méthode sera appelée pour les modifications, et en fonction de la charge utile, vous pouvez mettre à jour vos éléments recyclerview au lieu d'appeler notifyDataSetChanged ()
Modifiez votre méthode:
public void updateList(ArrayList<User> newList) { DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new MyDiffUtilCallBack(this.mDataset, newList)); this.mDataSet.clear() this.mDataSet.addAll(newList) diffResult.dispatchUpdatesTo(this); }
Cela semble correct. Veuillez publier la définition de
mAdapter
?ajoutée. Au fait, la récupération des données se trouve dans une méthode appelée par un événement onClick, je vais éditer le message pour l'expliquer. Pour une raison quelconque, la
updateList
n'est pas visible sur le mAdapter: /Avez-vous redéfini
est égal à
pour votre classeUser
? Sans cela,areContentsTheSame
ne fonctionnera pas correctement.Tu as raison, rien ne s'affiche, tu crois que c'est parce que les égaux? Pouvez-vous s'il vous plaît être plus précis? Je suis nouveau sur Android donc je ne sais pas comment le redéfinir
@ThibaultSeisel pouvez-vous s'il vous plaît ajouter comment dois-je le changer? Je ne peux pas le faire fonctionner