I am working on a chat app. I have the chat activity where the two users can send messages like WhatsApp, but I have a problem.
Like you can see in the picture (https://ibb.co/3cyYX01), the views are messing up when scrolling, and I think I know why.
After looking into those posts: RecyclerView messes up when scrolling , Android: RecyclerView content messed up after scrolling
I assume the problem may be in the recycler view adapter in the function onBindViewHolder
, because I am using the visibility option on some views(VIEW.GONE and VIEW.VISIBLE) and I think that these views are getting redrawn with wrong visibility.
In addition, I used holder.setIsRecyclable(false)
in onBindViewHolder
in order to check if it's the recycling part that cause the problem and when I used it, it worked perfectly.
This is the RecyclerView Adapter:
private const val SEND_LAYOUT = 0
private const val RECEIVED_LAYOUT = 1
class ChatRecyclerViewAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private lateinit var receiverUserPic: String
private lateinit var messageList: List<Message>
private lateinit var currentUserPic: String
private lateinit var currentUserUID: String
private lateinit var targetUID: String
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val viewHolder: RecyclerView.ViewHolder
val view: View
viewHolder = if (viewType == SEND_LAYOUT) {
view = LayoutInflater.from(parent.context)
.inflate(R.layout.sent_message_row, parent, false)
SentViewHolder(view)
} else {
view = LayoutInflater.from(parent.context)
.inflate(R.layout.recieved_message_row, parent, false)
ReceivedViewHolder(view)
}
return viewHolder
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
//holder.setIsRecyclable(false)
val currentMessage = messageList[position]
if (holder.itemViewType == SEND_LAYOUT) {
holder as SentViewHolder
holder.bindSentRow(currentMessage)
} else {
holder as ReceivedViewHolder
holder.bindReceivedRow(currentMessage)
}
}
override fun getItemCount(): Int {
return messageList.size
}
override fun getItemViewType(position: Int): Int {
val currentMessage = messageList[position]
return if (FirebaseAuth.getInstance().currentUser?.uid.equals(currentMessage.sender))
SEND_LAYOUT
else
RECEIVED_LAYOUT
}
inner class SentViewHolder(private val itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindSentRow(message: Message) {
val sentMessageTextView =
itemView.findViewById<TextView>(R.id.sentMessage)
val sentImage = itemView.findViewById<ImageView>(R.id.sentImage)
val profileImage =
itemView.findViewById<ImageView>(R.id.sentMessageProfilePicture)
val sentIsSeenImageTextView =
itemView.findViewById<TextView>(R.id.sentIsSeenImageTextView)
val sentIsSeenTextView =
itemView.findViewById<TextView>(R.id.sentIsSeenTextView)
profileImage.setOnClickListener {
val visitProfileIntent = Intent(it.context, VisitProfileActivity::class.java)
visitProfileIntent.putExtra("targetUID", currentUserUID)
it.context.startActivity(visitProfileIntent)
}
if (message.message.equals("Sent you an image") && !message.url.equals("")) {
sentMessageTextView.visibility = View.GONE
sentIsSeenImageTextView.visibility = View.VISIBLE
sentIsSeenTextView.visibility = View.GONE
sentImage.visibility = View.VISIBLE
Glide.with(itemView.rootView).load(message.url)
.override(SIZE_ORIGINAL, SIZE_ORIGINAL)
.error(R.drawable.error_icon)
.placeholder(R.drawable.loading_icon)
.listener(object : RequestListener<Drawable?> {
override fun onl oadFailed(
@Nullable e: GlideException?,
model: Any,
target: Target<Drawable?>,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable?>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
return false
}
}).into(sentImage)
if (adapterPosition == messageList.size - 1) {
sentIsSeenImageTextView.visibility = View.VISIBLE
sentIsSeenTextView.visibility = View.GONE
if (message.seen == true) {
sentIsSeenImageTextView.text = "Seen"
} else {
sentIsSeenImageTextView.text = "Sent"
}
} else {
sentIsSeenImageTextView.visibility = View.GONE
}
} else {
sentMessageTextView.visibility = View.VISIBLE
sentMessageTextView.text = message.message
sentIsSeenImageTextView.visibility = View.GONE
if (adapterPosition == messageList.size - 1) {
sentIsSeenTextView.visibility = View.VISIBLE
sentIsSeenImageTextView.visibility = View.GONE
if (message.seen == true) {
sentIsSeenTextView.text = "Seen"
} else {
sentIsSeenTextView.text = "Sent"
}
}
}
Glide.with(itemView.rootView).load(currentUserPic).into(profileImage)
}
}
inner class ReceivedViewHolder(private val itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindReceivedRow(message: Message) {
val receiveMessageTextView =
itemView.findViewById<TextView>(R.id.receivedMessage)
val receiveImage =
itemView.findViewById<ImageView>(R.id.receivedImage)
val receiveProfileImage =
itemView.findViewById<ImageView>(R.id.receivedMessageProfileImage)
receiveProfileImage.setOnClickListener {
val visitProfileIntent = Intent(it.context, VisitProfileActivity::class.java)
visitProfileIntent.putExtra("targetUID", targetUID)
it.context.startActivity(visitProfileIntent)
}
if (message.message.equals("Sent you an image") && !message.url.equals("")) {
receiveMessageTextView.visibility = View.GONE
receiveImage.visibility = View.VISIBLE
Glide.with(itemView.rootView).load(message.url).into(receiveImage)
} else {
receiveMessageTextView.visibility = View.VISIBLE
receiveMessageTextView.text = message.message
}
Glide.with(itemView.rootView).load(receiverUserPic).into(receiveProfileImage)
}
}
fun getMessageList(): List<Message> {
return messageList
}
fun setMessagesList(
newList: List<Message>,
userProfilePic: String,
userProfilePic1: String,
currentUID: String,
receiverUID: String
) {
messageList = newList
currentUserPic = userProfilePic
receiverUserPic = userProfilePic1
currentUserUID = currentUID
targetUID = receiverUID
notifyDataSetChanged()
}
}
Pastebin Link: https://pastebin.com/Ri5pUAdk
Thank you !
CodePudding user response:
Because the view holder is being recycled and reused that cause your views to be in the wrong state.
In SendViewHolder
class you only handle the state of sentImage
in if block by setting the visibility to visible. Therefore you also need to set its visibility to gone in else block.
Or you can reset the view visibility first then show it like below.
fun bindSendRow(message: Message) {
sentMessageTextView.visibility = View.GONE
sentImage.visibilty = View.GONE
if(shouldShowImage){
sentImage.visibility = View.VISIBLE
} else if(shouldShowText){
sentMessageTextView.visibility = View.VISIBLE
}
}
CodePudding user response:
Working of the recyleView
is based on that, it recycles views to show a list. When you scroll, views
which go out of the screen, are not destroyed but are reused again to show the new list item. So, if you change visibility
or any other property of a view and don't reset it again inside onBindViewHolder
, then it would show all the properties which were set earlier before it got recycled.
fun bind(data: Data) {
val textView = itemView.findViewById<TextView>(R.id.tvText)
if(data.text.isEmpty()) {
textView.visibility = View.GONE
}
}
In the above method, we are hiding textView
when text is empty
, but we are not setting anything in the else
condition. So when views
, for which we've set visibility to gone
would be recycled, they'd never show the textView
as it is reusing the view
. To deal with this, we've to set the properties of the views for true
and false
conditions each.
fun bind(data: Data) {
val textView = itemView.findViewById<TextView>(R.id.tvText)
if(data.text.isEmpty()) {
textView.visibility = View.GONE
} else {
textView.visibility = View.VISIBLE
}
}
In SentViewHolder
and ReceivedViewHolder
, you are setting the visibility of the ImageView
to visible
sentImage.visibility = View.VISIBLE
receiveImage.visibility = View.VISIBLE
but you are never setting it to gone
.
In the else condition of (message.message.equals("Sent you an image") && !message.url.equals(""))
, set the visibility
of ImageView
to GONE
. Do the same for all other views too, so you don't get an unexpected UI.