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 ViewHolder
s 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