Geek Dev
Geek Dev

Reputation: 51

Duplicate RecyclerView Item on Data Added or Deleted Firebase Firestore

I am building an app with firebase firestore as my database, i've added the pagination and it works but the issue is when data is added or deleted from the app the recycler item duplicates, sometimes into 3, 4 places and sometimes it messes up the arrangement. Have tried searching for solutions and ended up overriding these methods:

@Override
public long getItemId(int position) {
    return super.getItemId(position);
}

@Override
public int getItemViewType(int position) {
    return position;
}

and also adding

setHasStableIds(true);

but still no luck, here is my code, Fragment:

public class HomeFragment extends Fragment {

private View view;
private LinearLayout mNoMedia;
private RecyclerView mMediaList;
private SwipeRefreshLayout mSwipe;
private TextView mNoInternet;

private FirebaseAuth mAuth;
private FirebaseFirestore firebaseFirestore;

private List<PostModel> postModelList;
private PostAdapter postAdapter;
private DocumentSnapshot lastVisible;
private Boolean isFirstPageFirstLoad;
private String currentUserId;
private final int NUM_COLUMNS = 2;
private StaggeredGridLayoutManager staggeredGridLayoutManager;

public HomeFragment() {
    // Required empty public constructor
}

@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    if (view == null) {
        view = inflater.inflate(R.layout.fragment_home, container, false);

        mAuth = FirebaseAuth.getInstance();
        firebaseFirestore = FirebaseFirestore.getInstance();

        mSwipe = view.findViewById(R.id.mainSwipe);
        mMediaList = view.findViewById(R.id.mediaList);
        mNoInternet = view.findViewById(R.id.no_internet);
        postModelList = new ArrayList<>();

        staggeredGridLayoutManager = new StaggeredGridLayoutManager(NUM_COLUMNS, StaggeredGridLayoutManager.VERTICAL);
        postAdapter = new PostAdapter(postModelList);
        postAdapter.setHasStableIds(true);

        mMediaList.setLayoutManager(staggeredGridLayoutManager);
        mMediaList.hasFixedSize();
        mMediaList.setHasFixedSize(true);
        mMediaList.setItemAnimator(null);
        mMediaList.setAdapter(postAdapter);

        mNoMedia = view.findViewById(R.id.mainMediaLin);

        //=========================== Functions ====================================================
        try {
            if (getActivity() != null) {
                if (mAuth.getCurrentUser() != null) {
                    currentUserId = mAuth.getCurrentUser().getUid();

                        loadPost();
                    } else {
                        mNoMedia.setVisibility(View.VISIBLE);
                        mNoInternet.setVisibility(View.VISIBLE);
                    }
                } else {
                    mNoMedia.setVisibility(View.VISIBLE);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    return view;
}

private void loadPost() {
    Query firstQuery = firebaseFirestore.collection("TubbePosts")
            .document(currentUserId).collection("Posts")
            .orderBy("utc", Query.Direction.DESCENDING).limit(8);
    firstQuery.addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(@javax.annotation.Nullable QuerySnapshot queryDocumentSnapshots, @javax.annotation.Nullable FirebaseFirestoreException e) {
            if (queryDocumentSnapshots != null) {

                if (!queryDocumentSnapshots.isEmpty()) {
                    if (isFirstPageFirstLoad) {
                        lastVisible = queryDocumentSnapshots.getDocuments().get(queryDocumentSnapshots.size() - 1);
                        postModelList.clear();
                    }
                    for (@NonNull DocumentChange doc : queryDocumentSnapshots.getDocumentChanges()) {
                        if (doc.getType() == DocumentChange.Type.ADDED) {

                            mNoMedia.setVisibility(View.GONE);
                            String docId = doc.getDocument().getId();
                            final PostModel postModel = doc.getDocument().toObject(PostModel.class).withId(docId);

                            if (isFirstPageFirstLoad) {
                                postModelList.add(postModel);
                            } else {
                                postModelList.add(0, postModel);
                            }
                            postAdapter.notifyDataSetChanged();

                        }
                    }

                    isFirstPageFirstLoad = false;
                }
            }
        }
    });
}

public void loadMorePost() {

            Query nextQuery = firebaseFirestore.collection("TubbePosts")
                    .document(currentUserId).collection("Posts")
                    .orderBy("utc", Query.Direction.DESCENDING)
                    .startAfter(lastVisible).limit(8);
            nextQuery.addSnapshotListener(new EventListener<QuerySnapshot>() {
                @Override
                public void onEvent(@javax.annotation.Nullable QuerySnapshot queryDocumentSnapshots, @javax.annotation.Nullable FirebaseFirestoreException e) {
                    if (queryDocumentSnapshots != null) {
                        if (!queryDocumentSnapshots.isEmpty()) {
                            lastVisible = queryDocumentSnapshots.getDocuments().get(queryDocumentSnapshots.size() - 1);

                            for (@NonNull DocumentChange doc : queryDocumentSnapshots.getDocumentChanges()) {
                                if (doc.getType() == DocumentChange.Type.ADDED) {
                                    String docId = doc.getDocument().getId();
                                    @NonNull final PostModel postModel = doc.getDocument().toObject(PostModel.class).withId(docId);

                                    postModelList.add(postModel);
                                    postAdapter.notifyDataSetChanged();

                                }
                            }
                        }
                    }
                }
            });

}

My Adapter:

public class PostAdapter extends RecyclerView.Adapter<PostAdapter.ViewHolder> {

private List<PostModel> postList;
private Context context;
private FirebaseFirestore firebaseFirestore;
private FirebaseAuth firebaseAuth;
private String img = null;
private RequestOptions placeholder;
private String userId, id;
private int ref;

public PostAdapter(List<PostModel> postList) {
    this.postList = postList;
}

@NonNull
@Override
public PostAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_item, parent, false);
    context = parent.getContext();
    firebaseFirestore = FirebaseFirestore.getInstance();
    firebaseAuth = FirebaseAuth.getInstance();
    return new PostAdapter.ViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull PostAdapter.ViewHolder holder, int position) {

    holder.setIsRecyclable(false);

    try {
        final String docId = postList.get(position).DocId;
        final String currentUserId = firebaseAuth.getCurrentUser().getUid();

        String postId = postList.get(position).getPostId();
        String posterId = postList.get(position).getPosterId();
        String descText = postList.get(position).getDescText();
        String mediaUrl = postList.get(position).getMediaUrl();
        String format = postList.get(position).getFormat();
        long utcTime = postList.get(position).getUtc().getTime();
        String utc = postList.get(position).getUtc().toString();

        if (format.equals("image")) {
            holder.setImg(mediaUrl);

        } else if (format.equals("video")) {
            holder.mPlay.setVisibility(View.VISIBLE);
            holder.setVideoImg(mediaUrl);
        }
        holder.mTime.setText(getTimeAgo(utcTime, context));
        holder.mDesc.setText(descText);

        firebaseFirestore.collection("TubbePosts").document(posterId)
                .collection("Posts")
                .document(postId).addSnapshotListener(new EventListener<DocumentSnapshot>() {
            @Override
            public void onEvent(@javax.annotation.Nullable DocumentSnapshot documentSnapshot, @javax.annotation.Nullable FirebaseFirestoreException e) {
                if (documentSnapshot != null) {
                    if (!documentSnapshot.exists()) {
                        try {
                            userId = currentUserId;
                            id = postId;
                            ref = position;

                            new DeleteProcess().execute();

                        } catch (Exception f) {
                            f.printStackTrace();
                        }
                    }
                }
            }
        });

        firebaseFirestore.collection("Users").document(posterId)
                .addSnapshotListener(new EventListener<DocumentSnapshot>() {
                    @Override
                    public void onEvent(@javax.annotation.Nullable DocumentSnapshot documentSnapshot,
                                        @javax.annotation.Nullable FirebaseFirestoreException e) {
                        try {
                            if (documentSnapshot != null) {
                                if (documentSnapshot.exists()) {
                                    String image = documentSnapshot.getString("image");
                                    holder.setUserImg(image);
                                    img = image;
                                }
                            }

                        } catch (Exception f) {
                            f.printStackTrace();
                        }
                    }
                });

        firebaseFirestore.collection("TubbePosts").document(posterId)
                .collection("Posts")
                .document(postId).collection("Likes")
                .addSnapshotListener(new EventListener<QuerySnapshot>() {
                    @Override
                    public void onEvent(@Nullable QuerySnapshot queryDocumentSnapshots, @Nullable FirebaseFirestoreException e) {
                        if (queryDocumentSnapshots != null) {
                            if (!queryDocumentSnapshots.isEmpty()) {
                                int count = queryDocumentSnapshots.size();
                                if (count < 1000) {
                                    holder.mLikeCount.setText(count + "");
                                } else {
                                    holder.mLikeCount.setText(getCount(count));
                                }
                            } else {
                                holder.mLikeCount.setText("0");
                            }
                        }
                    }
                });

        firebaseFirestore.collection("TubbePosts").document(posterId)
                .collection("Posts").document(postId).collection("Likes")
                .document(currentUserId).addSnapshotListener(new EventListener<DocumentSnapshot>() {
            @Override
            public void onEvent(@Nullable DocumentSnapshot documentSnapshot, @Nullable FirebaseFirestoreException e) {
                if (documentSnapshot != null) {
                    if (documentSnapshot.exists()) {
                        holder.mLikeBtn.setImageDrawable(context.getResources().getDrawable(R.mipmap.heart_selected));
                    } else {
                        holder.mLikeBtn.setImageDrawable(context.getResources().getDrawable(R.mipmap.heart));
                    }
                }
            }
        });

        holder.mLikeBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isConnected(context)) {
                    firebaseFirestore.collection("TubbePosts")
                            .document(posterId)
                            .collection("Posts")
                            .document(postId).collection("Likes")
                            .document(currentUserId).get()
                            .addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
                                @Override
                                public void onComplete(@NonNull Task<DocumentSnapshot> task) {
                                    if (task.isSuccessful()) {
                                        if (task.getResult().exists()) {
                                            firebaseFirestore.collection("TubbePosts")
                                                    .document(posterId)
                                                    .collection("Posts")
                                                    .document(postId).collection("Likes")
                                                    .document(currentUserId).delete();
                                        } else {
                                            Date utc = new Date(System.currentTimeMillis());
                                            Map<String, Object> likesMap = new HashMap<>();
                                            likesMap.put("utc", utc);

                                            firebaseFirestore.collection("TubbePosts")
                                                    .document(posterId)
                                                    .collection("Posts")
                                                    .document(postId).collection("Likes")
                                                    .document(currentUserId).set(likesMap);
                                        }
                                    }
                                }
                            });

                }
            }
        });

        firebaseFirestore.collection("TubbePosts").document(posterId)
                .collection("Posts").document(postId).collection("Comments")
                .addSnapshotListener(new EventListener<QuerySnapshot>() {
                    @Override
                    public void onEvent(@Nullable QuerySnapshot queryDocumentSnapshots, @Nullable FirebaseFirestoreException e) {
                        if (queryDocumentSnapshots != null) {
                            if (!queryDocumentSnapshots.isEmpty()) {
                                int count = queryDocumentSnapshots.size();
                                if (count < 1000) {
                                    holder.mCommentCount.setText(count + "");
                                } else {
                                    holder.mCommentCount.setText(getCount(count));
                                }
                            } else {
                                holder.mCommentCount.setText("0");
                            }
                        }
                    }
                });


        holder.mPosterImg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!currentUserId.equals(posterId)) {
                    Intent viewIntent = new Intent(context, ViewAccountActivity.class);
                    viewIntent.putExtra("userId", posterId);
                    context.startActivity(viewIntent);
                } else {
                    Toast.makeText(context, context.getResources().getString(R.string.your_account), Toast.LENGTH_SHORT).show();
                   /* AccountFragment accountFragment = new AccountFragment();
                    HomeFragment homeFragment = new HomeFragment();
                    FragmentTransaction fragmentTransaction = homeFragment.getActivity()
                            .getSupportFragmentManager()
                            .beginTransaction();
                    fragmentTransaction.replace(R.id.main_frame, accountFragment);
                    fragmentTransaction.addToBackStack(null);
                    fragmentTransaction.commit(); */
                }
            }
        });

        holder.mCommentBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                Intent commentIntent = new Intent(context, CommentActivity.class);
                commentIntent.putExtra("posterId", posterId);
                commentIntent.putExtra("postId", postId);
                context.startActivity(commentIntent);
            }
        });

        holder.mCard.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent viewIntent = new Intent(context, ViewActivity.class);
                viewIntent.putExtra("postId", postId);
                viewIntent.putExtra("posterId", posterId);
                viewIntent.putExtra("desc", descText);
                viewIntent.putExtra("mediaUrl", mediaUrl);
                viewIntent.putExtra("posterImgUrl", img);
                viewIntent.putExtra("format", format);
                viewIntent.putExtra("utc", utc);
                context.startActivity(viewIntent);
            }
        });

    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Override
public int getItemCount() {
    return postList.size();
}

@Override
public long getItemId(int position) {
    return position;
}

@Override
public int getItemViewType(int position) {
    return position;
}

@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);
}

public class ViewHolder extends RecyclerView.ViewHolder {

    private View mView;
    private CardView mCard;
    private ImageView mPostImage, mLikeBtn, mCommentBtn, mPosterImg, mPlay;
    private TextView mDesc, mLikeCount, mCommentCount, mTime;

    public ViewHolder(View itemView) {
        super(itemView);

        mView = itemView;
        mCard = mView.findViewById(R.id.postCard);
        mPostImage = mView.findViewById(R.id.post_img);
        mPosterImg = mView.findViewById(R.id.posterImg);
        mDesc = mView.findViewById(R.id.postDesc);
        mLikeBtn = mView.findViewById(R.id.likeBtn);
        mLikeCount = mView.findViewById(R.id.likesCount);
        mCommentBtn = mView.findViewById(R.id.commentBtn);
        mCommentCount = mView.findViewById(R.id.commentCount);
        mTime = mView.findViewById(R.id.postTime);
        mPlay = mView.findViewById(R.id.play_hint);
    }

    private void setUserImg(String downloadUrl) {
        if (!TextUtils.isEmpty(downloadUrl)) {
            RequestOptions placeholderRequest = new RequestOptions();
            placeholderRequest.placeholder(R.color.grey200);
            Glide.with(context).setDefaultRequestOptions(placeholderRequest).load(downloadUrl).into(mPosterImg);
        }
    }

    private void setImg(String downloadUrl) {
        if (!TextUtils.isEmpty(downloadUrl)) {
            RequestOptions placeholderRequest = new RequestOptions();
            placeholderRequest.placeholder(R.drawable.ic_image);
            Glide.with(context).setDefaultRequestOptions(placeholderRequest).load(downloadUrl).into(mPostImage);
        }
    }

    private void setVideoImg(String downloadUrl) {
        if (!TextUtils.isEmpty(downloadUrl)) {
            long interval = getPosition() * 1000;
            RequestOptions options = new RequestOptions().frame(interval);
            placeholder = new RequestOptions();
            placeholder.placeholder(R.drawable.ic_image);
            Glide.with(context).setDefaultRequestOptions(placeholder).asBitmap()
                    .load(downloadUrl).apply(options).into(mPostImage);
        }
    }
}

And my Model:

public class PostModel extends DocId {

private String descText, postId, mediaUrl, posterId, format;
private Date utc;

public PostModel (){}

public PostModel(String descText, String postId, String mediaUrl, String posterId, String format, Date utc) {
    this.descText = descText;
    this.postId = postId;
    this.mediaUrl = mediaUrl;
    this.posterId = posterId;
    this.format = format;
    this.utc = utc;
}

public String getDescText() {
    return descText;
}

public String getPostId() {
    return postId;
}

public String getMediaUrl() {
    return mediaUrl;
}

public String getPosterId() {
    return posterId;
}

public String getFormat() {
    return format;
}

public Date getUtc() {
    return utc;
}

I appreciate any help.

Upvotes: 1

Views: 1585

Answers (2)

4xMafole
4xMafole

Reputation: 477

I had the same issue of data duplication on a recyclerView when calling data from firestore database. Using Java 8, it allows one to use a Collection Interface by calling the clear() method as follows:

listName.clear(); //clears the list
listName.add(new list(a,b)); //update the list with new data
adapter.notifyDataSetChanged(); //updates the data into a recyclerView

Also, if one is querying multiple data to display on a recyclerView, one can call a removeIf() method:

listName.removeIf(list -> list.getId() = 1); //Removes all of the elements of this collection that satisfy the given predicate.
listName.add(new list(a,b)); //update the list with new data
adapter.notifyDataSetChanged(); //updates the data into a recyclerView

The above methods clear() and removeIf(), did the job of avoiding data duplication in a recyclerView.

Upvotes: 1

Gaston Fassi Lavalle
Gaston Fassi Lavalle

Reputation: 488

My solution with .addSnapshotListener is using clear() in the mutable list before adding the documents:

productosLista.clear()
for (doc in documentSnapshots!!) {
    val producto = doc.toObject<Productos>(Productos::class.java)
    productosLista.add(producto)
}

Upvotes: 0

Related Questions