Дубликаты элементов в режиме рециркуляции с несколькими держателями при прокрутке

#android #firebase #firebase-realtime-database #android-recyclerview #android-viewholder

# #Android #firebase #firebase-база данных в реальном времени #android-recyclerview #android-viewholder

Вопрос:

Я создаю приложение для чата, которое должно раздувать два представления в RecyclerView (каждое для отправленных и полученных сообщений). Представления загружаются правильно при первой загрузке RecyclerView, но при прокрутке представления дублируются, а некоторые даже удаляются.

Я обнаружил, что причина этой проблемы связана с тем, как представление Recycler перерабатывает свои представления. Потому что, если я установлю holder.setIsRecyclable(false), этого не произойдет. Но я не хочу этого делать, поскольку это противоречит цели RecyclerView, а также прокрутка становится медленной.

Вот мой код адаптера:

 class MessageAdapter(
val db: FirebaseDatabase,
val auth: FirebaseAuth,
private val receiverUserID: String
) :
androidx.recyclerview.widget.ListAdapter<Chat, RecyclerView.ViewHolder>(
    DIFF_UTIL
) {

companion object {
    const val RIGHT_SIDE_VIEW_HOLDER = 0
    const val LEFT_SIDE_VIEW_HOLDER = 1

    val DIFF_UTIL = object : DiffUtil.ItemCallback<Chat>() {
        override fun areItemsTheSame(oldItem: Chat, newItem: Chat): Boolean {
            return oldItem.messageID == newItem.messageID
        }

        override fun areContentsTheSame(oldItem: Chat, newItem: Chat): Boolean {
            return oldItem == newItem
        }

    }
}



**override fun getItemViewType(position: Int): Int {
    
    return if (currentList[position].senderID == auth.currentUser!!.uid) {

        RIGHT_SIDE_VIEW_HOLDER //load if we sent that message
    } else {
       
        LEFT_SIDE_VIEW_HOLDER //load if we received that message
    }
}**


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
   
    return if (viewType == RIGHT_SIDE_VIEW_HOLDER) {
        //for loading the sent message(right hand side view)
        val binding = AdapterRightMessageHolderBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        RightMessageViewHolder(binding)
    } else {   //for loading the received message(left hand side view)
        val binding = AdapterLeftMessageHolderBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        LeftMessageViewHolder(binding)
    }
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

    when (holder.itemViewType) {

        //for the messages that we send
        RIGHT_SIDE_VIEW_HOLDER -> {
            (holder as RightMessageViewHolder).apply {

                //for image messages
                if (getItem(position).textMessage == IMAGE_MESSAGE amp;amp; getItem(position).imageUrl.isNotEmpty()) {
                    Glide.with(this.binding.root).load(getItem(position).imageUrl)
                        .into(this.binding.rightImageView)
                    this.binding.rightMessageCardView.visibility = GONE
                } else {
                    //for text message
                    this.binding.rightMessageTextView.text = getItem(position).textMessage
                    this.binding.rightImageCardView.visibility = GONE
                }

                //for SEEN message//we show if our message was the last message
                if (getItem(position) == currentList.last()) {
                    this.binding.rightSeenTextView.isVisible = true
                
                    if (getItem(position).seen) {
                        this.binding.rightSeenTextView.text = "Seen"
                    } else {
                       this.binding.rightSeenTextView.text = "Sent"
                    }

               } else {
                    this.binding.rightSeenTextView.visibility = GONE
                }
            }


        }

        //for the messages that we receive
        LEFT_SIDE_VIEW_HOLDER -> {
            (holder as LeftMessageViewHolder).apply {

                //for loading the profile pic
                db.getReference(USERS).child(receiverUserID)
                    .addListenerForSingleValueEvent(object : ValueEventListener {
                        override fun onDataChange(snapshot: DataSnapshot) {
                            val user = snapshot.getValue(User::class.java)
                            Glide.with(binding.root).load(user?.profilePic)
                                .into(binding.leftReceiverProfilePic)
                        }

                        override fun onCancelled(error: DatabaseError) {
                            TODO("Not yet implemented")
                        }

                    })

                //we don't load the profile pic of the person on the left if they send a CONSECUTIVE MESSAGE
                if (holder.adapterPosition > 0) {
                    if (getItem(position).senderID == getItem(position - 1).senderID) {
                        this.binding.leftReceiverProfilePic.visibility = INVISIBLE
                    } else {
                        this.binding.leftReceiverProfilePic.visibility = VISIBLE
                    }
                }

                //for image messages
                if (getItem(position).textMessage == IMAGE_MESSAGE amp;amp; getItem(position).imageUrl.isNotEmpty()) {

                    Glide.with(this.binding.root).load(getItem(position).imageUrl)
                        .into(this.binding.leftImageView)
                    this.binding.leftMessageCardView.visibility = GONE

                } else {
                    //for text message
                    this.binding.leftMessageTextView.text = getItem(position).textMessage
                    this.binding.leftImageCardView.visibility = GONE

                }


            }

        }
    }
}


//view holder for inflating the view of the messages that we RECEIVE
inner class LeftMessageViewHolder(val binding: AdapterLeftMessageHolderBinding) :
    RecyclerView.ViewHolder(binding.root) {


}

//view holder for inflating the view of the messages that we SEND
inner class RightMessageViewHolder(val binding: AdapterRightMessageHolderBinding) :
    RecyclerView.ViewHolder(binding.root) {


}}
 

Фрагмент:

 messageRecyclerView.apply {
        
        val linearLayoutManager = LinearLayoutManager(context)
        
        //linearLayoutManager.stackFromEnd = true

        this.layoutManager = linearLayoutManager

        this.adapter = messageAdapter

    }

db.getReference(CHATS).child(chatUID).addValueEventListener(object : ValueEventListener {
        override fun onDataChange(snapshot: DataSnapshot) {
            Log.d(TAG, "onDataChange: Messages being loaded")
            if (snapshot.exists()) {
                chatList.clear()
                for (child in snapshot.children) {
                    val message = child.getValue(Chat::class.java)
                    chatList.add(message!!)

                    //submit the list
                    messageAdapter.submitList(chatList.toList())
                }


            }



        }

        override fun onCancelled(error: DatabaseError) {
            TODO("Not yet implemented")
        }

    })
 

Я провел некоторое тестирование и обнаружил, что это НЕ является причиной:

  1. Наличие нескольких держателей просмотра. То же самое происходит, когда я использую один ViewHolder.
  2. Представления в onBindViewHolder() имеют значение VISIBLE / INVISIBLE / GONE. Я установил для всех представлений значение VISIBLE, и это все равно произошло.

Я на 100% уверен, что эта проблема как-то связана с тем, как RecyclerView перерабатывает свои представления, но я не знаю, как исправить мой код в соответствии с этим. Любая помощь приветствуется!

Комментарии:

1. как вы это решили?

2. @ankalagba Я на самом деле не решал эту проблему. Но я обнаружил, что причиной проблемы был ListAdapter / DiffUtil, поскольку он начал работать нормально, когда я использовал обычный RecyclerViewAdapter и просто вызвал notifyDataSetChanged() из фрагмента для обновления списка. :/