Skip to content
Advertisement

Incorrect displaying items in RecyclerView while scrolling

Could you please assist in following issue:

I have incorrect displaying items in my messenger app.

My layout for items:

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginHorizontal="10dp"
    android:layout_marginTop="10dp"
    app:cardElevation="0dp">

        <TextView
            android:id="@+id/message_my_message"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="end"
            android:gravity="end"
            android:textColor="@color/black"
            android:textSize="14sp"
            android:visibility="invisible"
            />

        <TextView
            android:id="@+id/message_your_message"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="start"
            android:gravity="start"
            android:textColor="@color/black"
            android:textSize="14sp"
            android:visibility="invisible"
            />

</com.google.android.material.card.MaterialCardView>

My layout for dialog activity:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".DialogActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/dialog_appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/Theme.AnonymousAd.AppBarOverlay">

        <include
            android:id="@+id/dialog_app_bar"
            layout="@layout/app_bar_layout"
            >

        </include>

    </com.google.android.material.appbar.AppBarLayout>



        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/dialog_send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:layout_alignParentBottom="true"
            android:clickable="true"
            android:contentDescription="TODO"
            android:focusable="true"
            android:src="@drawable/ic_send"
            android:visibility="invisible"
            app:fabSize="mini"
            tools:ignore="SpeakableTextPresentCheck" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/dialog_attach"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:layout_alignParentBottom="true"
            android:clickable="true"
            android:contentDescription="TODO"
            android:focusable="true"
            android:src="@drawable/ic_attach"
            app:fabSize="mini"
            tools:ignore="SpeakableTextPresentCheck" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/dialog_gift"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_alignParentBottom="true"
            android:clickable="true"
            android:contentDescription="TODO"
            android:focusable="true"
            android:src="@drawable/ic_gift"
            app:fabSize="mini"
            tools:ignore="SpeakableTextPresentCheck" />

        <EditText
            android:id="@+id/dialog_message"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_toLeftOf="@+id/dialog_send"
            android:layout_toRightOf="@+id/dialog_gift"
            android:autofillHints=""
            android:backgroundTint="@color/teal_700"
            android:hint="@string/dialog_enter_message"
            android:inputType="textCapSentences|textMultiLine"
            android:maxHeight="120dp"
            android:maxLength="500"
            android:minHeight="48dp"
            android:textColorHint="#757575"
            android:textSize="16sp" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/dialog_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_above="@+id/dialog_message"
            android:layout_below="@+id/dialog_appbar"
            android:layout_marginBottom="5dp"
            android:focusedByDefault="true"
            android:scrollbars="vertical" />

Class DialogActivity

public class DialogActivity extends AppCompatActivity {

    private RecyclerView dialogRecyclerView;
    private LinearLayoutManager layoutManager;
    private final List<Message> messageList = new ArrayList<>();
    private MessageAdapter messageAdapter;
    private String idText;
    private String userNameText;
    private EditText dialogMessage;
    private Toolbar toolbar;
    private FloatingActionButton dialogSend;
    private FloatingActionButton dialogAttach;
    private String downloadedImageUrl;
    private StorageTask uploadTask;
    private Uri imageUri;
    private DatabaseReference dialogsDataBase;
    private ProgressDialog loadingBar;

    private FirebaseFirestore db = FirebaseFirestore.getInstance();
    private CollectionReference answersDataBase = db.collection("AnswersDataBase");
    private StorageReference imageDataBase;

    @SuppressLint("WrongViewCast")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dialog);

        idText = getIntent().getExtras().get("idText").toString();
        userNameText = getIntent().getExtras().get("userNameText").toString();

        dialogsDataBase = FirebaseDatabase.getInstance().getReference().child("DialogsDataBase").child(idText);
        imageDataBase = FirebaseStorage.getInstance().getReference().child("imageDataBase");

        toolbar = findViewById(R.id.dialog_app_bar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setTitle(userNameText);

        messageAdapter = new MessageAdapter(this, messageList);
        dialogRecyclerView = findViewById(R.id.dialog_recycler_view);
        dialogRecyclerView.setHasFixedSize(true);
        layoutManager = new LinearLayoutManager(this);
        layoutManager.setStackFromEnd(true);
        dialogRecyclerView.setLayoutManager(layoutManager);
        dialogRecyclerView.setAdapter(messageAdapter);

        loadingBar = new ProgressDialog(this);

        dialogMessage = findViewById(R.id.dialog_message);

        dialogAttach = findViewById(R.id.dialog_attach);

        dialogAttach.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.setAction(Intent.ACTION_GET_CONTENT);
                intent.setType("image/*");
                //startActivityForResult(intent, 438);
                someActivityResultLauncher.launch(intent);
            }
        });

        dialogMessage.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                if(dialogMessage.getText().toString().length() > 0){
                    dialogAttach.setVisibility(View.INVISIBLE);
                    dialogSend.setVisibility(View.VISIBLE);
                }
                else {
                    dialogAttach.setVisibility(View.VISIBLE);
                    dialogSend.setVisibility(View.INVISIBLE);
                }
            }

            @Override
            public void afterTextChanged(Editable editable) {

            }
        });

        dialogSend = findViewById(R.id.dialog_send);

        dialogSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(dialogMessage.getText().toString().length() > 0){
                    answersDataBase.document(idText).update("answer", dialogMessage.getText().toString());
                    dialogsDataBase.push().setValue(new Message(Paper.book().read("userName"), dialogMessage.getText().toString(), "text"));
                    dialogMessage.setText("");
                }
            }
        });

    }

    @Override
    protected void onStart() {
        super.onStart();

        dialogsDataBase.addChildEventListener(new ChildEventListener() {
            @Override
            public void onChildAdded(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {
                Message message = snapshot.getValue(Message.class);

                messageList.add(message);

                messageAdapter.notifyDataSetChanged();

                dialogRecyclerView.smoothScrollToPosition(dialogRecyclerView.getAdapter().getItemCount());
            }

            @Override
            public void onChildChanged(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {

            }

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

            }

            @Override
            public void onChildMoved(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {

            }

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

            }
        });
    }

    ActivityResultLauncher<Intent> someActivityResultLauncher = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            new ActivityResultCallback<ActivityResult>() {
                @Override
                public void onActivityResult(ActivityResult result) {
                    if (result.getResultCode() == Activity.RESULT_OK) {

                        loadingBar.setTitle("Sending Image");
                        loadingBar.setMessage("Please wait, we are sending that image");
                        loadingBar.setCanceledOnTouchOutside(false);
                        loadingBar.show();

                        // There are no request codes
                        Intent data = result.getData();
                        imageUri = data.getData();

                        StorageReference filePath = imageDataBase.child(System.currentTimeMillis() + ".jpg");

                        uploadTask = filePath.putFile(imageUri);
                        uploadTask.continueWithTask(new Continuation() {
                            @Override
                            public Object then(@NonNull Task task) throws Exception {

                                if(!task.isSuccessful()){
                                    throw task.getException();
                                }

                                return filePath.getDownloadUrl();
                            }
                        }).addOnCompleteListener(new OnCompleteListener<Uri>() {
                            @Override
                            public void onComplete(@NonNull Task<Uri> task) {

                                if(task.isSuccessful()){

                                    Uri downloadUrl = task.getResult();
                                    downloadedImageUrl = downloadUrl.toString();

                                    answersDataBase.document(idText).update("answer", "Photo");
                                    dialogsDataBase.push().setValue(new Message(Paper.book().read("userName"), downloadedImageUrl, "image"));
                                    loadingBar.dismiss();
                                    recreate();
                                }

                            }
                        });
                    }
                }
            });

    @Override
    protected void onDestroy() {
        super.onDestroy();
        messageList.clear();
    }

    @Override
    protected void onPause() {
        super.onPause();
        messageList.clear();
    }
}

Adapter class

public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.MessageViewHolderNew> {

    private Context context;
    private List<Message> messagesList;
    private Dialog dialog;
    private String userNameString;

    public MessageAdapter(Context context, List<Message> messagesList){

        this.context = context;
        this.messagesList = messagesList;
        userNameString = Paper.book().read("userName");

    }

    public class MessageViewHolderNew extends RecyclerView.ViewHolder{

        private TextView messageMyMessage;
        private TextView messageYourMessage;
        private ImageView messageMyImage;
        private ImageView messageYourImage;



        public MessageViewHolderNew(@NonNull View itemView) {
            super(itemView);

            messageMyMessage = (TextView) itemView.findViewById(R.id.message_my_message);
            messageYourMessage = (TextView) itemView.findViewById(R.id.message_your_message);
            messageMyImage = (ImageView) itemView.findViewById(R.id.message_my_image);
            messageYourImage = (ImageView) itemView.findViewById(R.id.message_your_image);

        }
    }

    @NonNull
    @Override
    public MessageViewHolderNew onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.message_items_layout, parent, false);

        return new MessageViewHolderNew(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MessageViewHolderNew holder, int position) {

        if(messagesList.get(position).getUserName().equals(userNameString)){
            holder.messageMyMessage.setText(messagesList.get(position).getMessage());
            holder.messageMyMessage.setVisibility(View.VISIBLE);
        }
        else{
            holder.messageYourMessage.setText(messagesList.get(position).getMessage());
            holder.messageYourMessage.setVisibility(View.VISIBLE);
        }

   }

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

}

In this place I check user name.

   if(messagesList.get(position).getUserName().equals(userNameString)){
            holder.messageMyMessage.setText(messagesList.get(position).getMessage());
            holder.messageMyMessage.setVisibility(View.VISIBLE);
        }
        else{
            holder.messageYourMessage.setText(messagesList.get(position).getMessage());
            holder.messageYourMessage.setVisibility(View.VISIBLE);
        }

If user name is my name then I set message and visible to holder.messageMyMessage. Else I set message and visible to holder.messageYourMessage. But sometimes message and visible are set to both messages while scrolling or sent new message. . See attached screenshot for details

Advertisement

Answer

Your problem’s here:

if(messagesList.get(position).getUserName().equals(userNameString)){
    holder.messageMyMessage.setText(messagesList.get(position).getMessage());
    holder.messageMyMessage.setVisibility(View.VISIBLE);
} else {
    holder.messageYourMessage.setText(messagesList.get(position).getMessage());
    holder.messageYourMessage.setVisibility(View.VISIBLE);
}

You’re only making stuff visible, you’re never hiding the other thing.

So if a specific ViewHolder is used to display one kind of message, it’ll be made visible in onBindViewHolder. The view in that ViewHolder’s layout will be set to VISIBLE.

Then, if you scroll down the list and the same ViewHolder is reused to display the other kind of message, the other message view in the layout will be set to VISIBLE. The other message view hasn’t changed, it’s still in the same state, VISIBLE. So you see them both (depending on how the layout works, one might be covering the other).

When you’re using a RecyclerView, because the ViewHolders are reused (recycled) you need to set them up correctly for each item, clearing all the previous state. So in your case, for each message display, you have to either make it visible or hide it. You can’t do nothing, because that can leave you with old state from the previous item it was displaying, right?

So you need to do this:

if(messagesList.get(position).getUserName().equals(userNameString)){
    holder.messageMyMessage.setText(messagesList.get(position).getMessage());
    holder.messageMyMessage.setVisibility(View.VISIBLE);
    // now you need to make sure the other is -not- visible!
    holder.messageYourMessage.setVisibility(View.GONE);
} else {
    holder.messageYourMessage.setText(messagesList.get(position).getMessage());
    holder.messageYourMessage.setVisibility(View.VISIBLE);
    // same thing - explicitly hide the other one, assume it could be visible
    holder.messageMyMessage.setVisibility(View.GONE);
}

This is the number one thing you need to do with a RecyclerView – in onBindViewHolder, always set up everything that can change depending on the item. Assume it has old data or the wrong thing set, and explicitly initialise everything. It’s like a whiteboard – when you start using it you need to clean it, because maybe there’s some old stuff on there

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement