0

I have several items in a ListItemView / RecyclerView. When items are added it correctly updates the view and the items are displayed in the order that they are added.

However, when I go above a number of items so that all the views fill the screen (scrolling becomes possible), it displays the first item again as a duplicate, instead of displaying the latest added item.

My GetView method:

@NonNull
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View listItemView = convertView;

    Log.d(TAG, "The GetView method was called");

    ChatConversationMessage currentMessage = getItem(position);

    if (listItemView == null) {
        if (currentMessage.getSentTextMessage() != null) {

            Log.d(TAG, "Sent message detected");
            // Sent text
            listItemView = LayoutInflater.from(getContext()).inflate(
                    R.layout.chat_conversation_txt_sent, parent, false);
            TextView sentText = listItemView.findViewById(R.id.textview_chat_message_sent);
            sentText.setText(currentMessage.getSentTextMessage());
        }

        if (currentMessage.getReceivedTextMessage() != null) {

            Log.d(TAG, "Received message detected");
            // Received text
            listItemView = LayoutInflater.from(getContext()).inflate(
                    R.layout.chat_conversation_txt_received, parent, false);
            TextView receivedText = listItemView.findViewById(R.id.textview_chat_message_received);
            receivedText.setText(currentMessage.getReceivedTextMessage());
            ImageView chatHead = listItemView.findViewById(R.id.imageview_chat_head_received);
            chatHead.setImageResource(currentMessage.getImageResourceId());
            chatHead.setClipToOutline(true);
        }
    }

    return listItemView;
}

And my getLiveMessages method (From Firestore)

public void getLiveChatMessages(final ArrayList<ChatConversationMessage> messageArrayList, final ChatConversationMessageAdapter adapter) {

            messagesCollectionRef.addSnapshotListener(new EventListener<QuerySnapshot>() {
                @Override
                public void onEvent(@Nullable QuerySnapshot snapshots,
                                    @Nullable FirebaseFirestoreException e) {
                    if (e != null) {
                        Log.w(TAG, "listen:error", e);
                        return;
                    }

                    for (DocumentChange dc : snapshots.getDocumentChanges()) {

                        switch (dc.getType()) {
                            case ADDED:
                                Log.d(TAG, "New message added" + dc.getDocument().getData());

                                if (dc.getDocument().get("Message") != null && dc.getDocument().get("From user with ID").equals(userID)) {
                                    String message = dc.getDocument().getString("Message");
                                    messageArrayList.add(new ChatConversationMessage(message));
                                    adapter.notifyDataSetChanged(); //Ensures messages are visible immediately

                                } else if (dc.getDocument().get("Message") != null) {
                                    String message = dc.getDocument().getString("Message");
                                    Log.e("TAG", "The received message is written to the array. Message: " + message);
                                    messageArrayList.add(new ChatConversationMessage(message, R.drawable.redhead_1));

                                    adapter.notifyDataSetChanged(); //Ensures messages are visible immediately

                                }
                                Log.d(TAG, "Message ArrayList: " +  messageArrayList);
                                break;

                            case MODIFIED:
                                Log.d(TAG, "Message modified" + dc.getDocument().getData());
                                break;
                            case REMOVED:
                                Log.d(TAG, "Message removed" + dc.getDocument().getData());
                                break;
                        }
                    }

                }
            });
}
The Fluffy T Rex
  • 430
  • 7
  • 22

1 Answers1

0

You're not handling view "recycling" correctly. getView() must always return a view and set the subview values, the latter is where you're running into trouble.

This is annotated code showing some suggestions to solve your problem:

public View getView(int position, View convertView, ViewGroup parent) {
    View listItemView = convertView;

    Log.d(TAG, "The GetView method was called");

    ChatConversationMessage currentMessage = getItem(position);

    // determine if Sent Message
    if (currentMessage.getSentTextMessage() != null) 
    {

        Log.d(TAG, "Sent message detected");

        if (listItemView == null) {
            // inflate the layout as needed
            listItemView = LayoutInflater.from(getContext()).inflate(
                    R.layout.chat_conversation_txt_sent, parent, false);
        }

        // now set the subview values
        TextView sentText = listItemView.findViewById(R.id.textview_chat_message_sent);
        sentText.setText(currentMessage.getSentTextMessage());
    }

    // else a Received Message
    else if (currentMessage.getReceivedTextMessage() != null) {

        Log.d(TAG, "Received message detected");

        if (listItemView == null) {
            // inflate the layout as needed
            listItemView = LayoutInflater.from(getContext()).inflate(
                    R.layout.chat_conversation_txt_received, parent, false);
        }

        // then set the subview values
        TextView receivedText =  listItemView.findViewById(R.id.textview_chat_message_received);
        receivedText.setText(currentMessage.getReceivedTextMessage());
        ImageView chatHead = listItemView.findViewById(R.id.imageview_chat_head_received);
        chatHead.setImageResource(currentMessage.getImageResourceId());
        chatHead.setClipToOutline(true);
    }

    return listItemView;
}

You should consider a ViewHolder for your two layouts, this will simplify your code and make it more efficient at run time. Not sure if this is the best link but should give you some additional ideas:

About RecyclerView.ViewHolder and RecyclerView.Adapter

CSmith
  • 13,318
  • 3
  • 39
  • 42
  • I tried with your method, but the app crashed on the "setText" methods saying it cannot invoke it on a null object reference. So I tried moving the setText methods (and the rest of the code) within each if-statement block. But that didn't work either as it messes up the order of my items :-( – The Fluffy T Rex Sep 17 '18 at 20:40
  • the crash is likely because you're not using RecyclerView correctly ... you've got two different views (Sent and Received). See https://stackoverflow.com/questions/26245139/how-to-create-recyclerview-with-multiple-view-type – CSmith Sep 18 '18 at 19:27
  • ,,,more specifically, if convertView is a chat_conversation_txt_sent and the currentMessage is a received message, then listItemView will refer to the wrong viewGroup – CSmith Sep 18 '18 at 20:18
  • Thanks. I implemented the whole thing from scratch based on SendBird's chat app tutorial and saw a lot of things were missing. Works now! – The Fluffy T Rex Sep 20 '18 at 09:27