Home > Back-end >  RecyclerView Messing Up Views When Scrolling - With Picture
RecyclerView Messing Up Views When Scrolling - With Picture

Time:04-28

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.

  • Related