#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")
}
})
Я провел некоторое тестирование и обнаружил, что это НЕ является причиной:
- Наличие нескольких держателей просмотра. То же самое происходит, когда я использую один ViewHolder.
- Представления в onBindViewHolder() имеют значение VISIBLE / INVISIBLE / GONE. Я установил для всех представлений значение VISIBLE, и это все равно произошло.
Я на 100% уверен, что эта проблема как-то связана с тем, как RecyclerView перерабатывает свои представления, но я не знаю, как исправить мой код в соответствии с этим. Любая помощь приветствуется!
Комментарии:
1. как вы это решили?
2. @ankalagba Я на самом деле не решал эту проблему. Но я обнаружил, что причиной проблемы был ListAdapter / DiffUtil, поскольку он начал работать нормально, когда я использовал обычный RecyclerViewAdapter и просто вызвал notifyDataSetChanged() из фрагмента для обновления списка. :/