How to delete from RecyclerView when using ChildEventListener along with a limitToLast Query

2019-11-03 android firebase firebase-realtime-database android-recyclerview

I am trying to remove items from RecyclerView, I'm using ChildEventListener along with a limitToLast query. Apparently when an item is added onChildRemoved is fired before onChildAdded to remove the item at index 0 first, similarily when I delete an item onChildAdded is fired after onChildRemoved which adds the old item(older than the one at index 0) back to list, which causes this old item to appear at the bottom of the list. Is there some way by which I can avoid this and add the old item back to index 0.

I have tried using a flag that I set inside my delete method to identify that some item is deleted by me and the old item should be added index 0 instead of index n. This, of course, is useless as this wouldn't synchronize the behavior on other devices except for mine because their flag value wouldn't be updated.

Here is my ChildEventListener

Query lastQuery = databaseReference.child("Private Chats").child(chatkey).orderByKey().limitToLast(120);
        lastQuery.addChildEventListener(new ChildEventListener() {
            @Override
            public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
                ChatMessage value = dataSnapshot.getValue(ChatMessage.class);
                ChatMessage fire = new ChatMessage();
                String msgtxt = value.getMessageText();
                String user = value.getMessageUser();
                long msgtime = value.getMessageTime();
                String prothumb = value.getProfuri();
                String type = value.getType();
                String sentimguri = value.getSentimguri();
                Boolean seen = value.getSeen();
                fire.setMessageUser(user);
                fire.setMessageText(msgtxt);
                fire.setMessageTime(msgtime);
                fire.setProfuri(prothumb);
                fire.setType(type);
                fire.setSentimguri(sentimguri);
                fire.setSeen(seen);

        //**PROBLEM** The flag I set in my delete method to identify if the item should be added at 
        //bottom or top does not work for others as they don't have the flag value updated

           if (flag != 2) {
                    keys.add(dataSnapshot.getKey());
                    list.add(fire);
                }
                //sync deletion
                String key=dataSnapshot.getKey();
                int index=keys.indexOf(key);
                if(flag==2)
                {
                    list.add(0,fire);
                    keys.add(0,key);
                }
                Log.d("Added ","By "+Integer.toString(index));
                mAdapter.notifyItemInserted(index);
                if (!(mRecyclerView.canScrollVertically(1))) {
                    mRecyclerView.smoothScrollToPosition(mAdapter.getItemCount());
                    newmessages.clearAnimation();
                    newmessages.setVisibility(View.GONE);


                } else {
                    if (flag == 0) {

                        newmessages.setVisibility(View.VISIBLE);
                        newmessages.startAnimation(AnimationUtils.loadAnimation(PrivateMessage.this, R.anim.bounceanim));
                    }
                }
                setFlag(0);

            }

            @Override
            public void onChildChanged(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
                //Updates the message seen/delivered field.

                ChatMessage value = dataSnapshot.getValue(ChatMessage.class);
                String key = dataSnapshot.getKey();
                int index = keys.indexOf(key);
                ChatMessage fire = new ChatMessage();
                String msgtxt = value.getMessageText();
                String user = value.getMessageUser();
                long msgtime = value.getMessageTime();
                String prothumb = value.getProfuri();
                String type = value.getType();
                String sentimguri = value.getSentimguri();
                Boolean seen = value.getSeen();
                fire.setMessageUser(user);
                fire.setMessageText(msgtxt);
                fire.setMessageTime(msgtime);
                fire.setProfuri(prothumb);
                fire.setType(type);
                fire.setSentimguri(sentimguri);
                fire.setSeen(seen);
                Log.d("Changed at ", index + fire.getSeen().toString());
                list.set(index, fire);
                mAdapter.notifyItemChanged(index);
            }

            @Override
            public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) {

                    String key = dataSnapshot.getKey();
                    int index = keys.indexOf(key);
                    keys.remove(key);
                    list.remove(index);
                    Log.d("Deletion", "at " + Integer.toString(index));
                    mAdapter.notifyItemRemoved(index);             

            }

            @Override
            public void onChildMoved(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

            }

            @Override
            public void onCancelled(@NonNull DatabaseError databaseError) {

            }
        });

Here is my delete method

 void deleteMessage(int position) {
        String key = keys.get(position);
        chatkey = ((PrivateMessage) mContext).getChatkey();
        DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("Private Chats").child(chatkey);
         ref.child(key).removeValue();
        ((PrivateMessage)mContext).setFlag(2);
        Toast.makeText(mContext, "Deleted", Toast.LENGTH_SHORT).show();

    }

If anyone has any idea or a different approach on how to add the item at the top instead of the bottom of the list so it synchronizes on the recipient's device too, please help me I'm scratching my head over this for a few days.

P.S I was using ValueEventListener before but it glitches the recyclerview too much the views flashed at random postions for a second after deletion.

Answers

I ran into the same problem and this is how I solved it: First, in the onChildAdded check to make sure your list, keys, doesn't already have the child being added before doing all the work inside the onChildAdded. Then I went ahead and created a boolean variable that will hold whether or not the child in question is older than the one at the end of your list. This is the code adapted to yours:

public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
if (!keys.contains(dataSnapshot.getKey())){
    boolean isOld;//check if is an old message

    //This code compares the last key in your list (if any) to the new key being added
    isOld = !list.isEmpty() && keys.get(list.size()-1).compareTo(dataSnapshot.getKey()) > 0;

   //Add your ChatMessage code here

   //Decide where to add the message
   if (isOld){ //add to top of list
      list.add(0,fire);
      keys.add(0,dataSnapshot.getKey());
      mAdapter.notifyItemInserted(0); //Decide if you need this or not
   }
   else{ //add to bottom
      list.add(newMessage);
      keys.add(dataSnapshot.getKey());
      mAdapter.notifyItemInserted(lstMessages.size() - 1);
   }


  }
}

I hope this solves the issue if you still have it, or helps someone else. This is here for me in the future as well.

Related