You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Firestore获取Receiver Room ID时RecyclerView消息重复排查

问题分析:RecyclerView消息重复与双房间数据获取优化

除了获取发送方房间(sender room)的数据外,还需要获取接收方房间(receiver room)的ID以实现双方消息删除功能,但当前RecyclerView出现消息重复问题,需排查代码错误。

原代码

private fun setDataToAdapter(){
        firestore.collection("Chats").document(senderRoom!!).collection("messages").orderBy("time")
            .addSnapshotListener { value, _ ->
                if (!value?.isEmpty!!) {
                    messages.clear()
                    for (document in value.documents) {
                        val coMsg = document.data?.get("message").toString()
                        val coId = document.data?.get("senderId").toString()
                        val msgId = document.id
                        //Using Date
                        // Get Calendar
                        val calendarMap = document.data?.get("date") as Map<*, *>
                        val curTimestamp = calendarMap["time"] as com.google.firebase.Timestamp
                        val millis = curTimestamp.seconds * 1000 + curTimestamp.nanoseconds / 1000000
                        val ss = SimpleDateFormat("hh:mm a", Locale.getDefault())
                        val netCal = Date(millis)
                        val msgTime = ss.format(netCal).toString()
                        // Get Day
                        val chatDay = document.data?.get("day").toString()
                        // Get Full Date
                        val fullDateStamp = calendarMap["time"] as com.google.firebase.Timestamp
                        val dateInMillis = fullDateStamp.seconds * 1000 + fullDateStamp.nanoseconds / 1000
                        val sdf = SimpleDateFormat("MM/dd/yyyy", Locale.getDefault())
                        val netAllDate = Date(dateInMillis)
                        val fullDate = sdf.format(netAllDate).toString()
                        val type = document.data?.get("type").toString()
                        firestore.collection("Chats").document(receiverRoom!!)
                            .collection("messages").orderBy("time").addSnapshotListener { value1, _ ->
                                if (!value1?.isEmpty!!) {
                                    for (document1 in value.documents) {
                                        val id = document1.id
                                        val coMsgT = SingleChatData(coMsg, coId, msgId, msgTime, chatDay, fullDate, type, id)
                                        messages.add(coMsgT)
                                    }
                                    recyclerView.post { // Call smooth scroll
                                        recyclerView.smoothScrollToPosition(adapter.itemCount - 1)
                                    }
                                    adapter.notifyDataSetChanged()
                                }
                            }
                    }
                }
            }
    }

核心错误点

  1. 嵌套监听+循环重复添加:在发送方房间的每条消息循环里,都注册了接收方房间的快照监听,每次发送方有新消息时,都会触发接收方的监听;同时遍历的是value.documents(发送方的消息列表)而非value1.documents(接收方的),导致发送方消息被重复添加N次(N为发送方消息条数)。
  2. 未清理旧监听:每次调用setDataToAdapter都会新增监听,旧监听不会自动移除,多次触发回调后加剧消息重复问题。
  3. 数据逻辑冗余:聊天消息在发送方和接收方房间是镜像存储的,无需同时拉取两边数据,只需拉取其中一方的消息,同时关联对应房间的消息ID即可。

修复后的代码示例

// 全局变量存储监听,方便页面销毁时移除
private var senderMsgListener: ListenerRegistration? = null

private fun setDataToAdapter(){
    // 先移除旧监听,避免重复回调
    senderMsgListener?.remove()
    messages.clear()

    // 只拉取发送方房间的消息,同时获取关联的接收方消息ID
    senderMsgListener = firestore.collection("Chats").document(senderRoom!!)
        .collection("messages").orderBy("time")
        .addSnapshotListener { value, _ ->
            if (value?.isEmpty != true) {
                messages.clear()
                for (document in value.documents) {
                    val coMsg = document.data?.get("message").toString()
                    val coId = document.data?.get("senderId").toString()
                    val senderMsgId = document.id
                    // 假设发送消息时,已将接收方房间的消息ID存入当前消息的receiverMsgId字段
                    val receiverMsgId = document.data?.get("receiverMsgId").toString()

                    // 日期处理逻辑保留
                    val calendarMap = document.data?.get("date") as Map<*, *>
                    val curTimestamp = calendarMap["time"] as com.google.firebase.Timestamp
                    val millis = curTimestamp.seconds * 1000 + curTimestamp.nanoseconds / 1000000
                    val ss = SimpleDateFormat("hh:mm a", Locale.getDefault())
                    val msgTime = ss.format(Date(millis)).toString()
                    
                    val chatDay = document.data?.get("day").toString()
                    
                    val fullDateStamp = calendarMap["time"] as com.google.firebase.Timestamp
                    val dateInMillis = fullDateStamp.seconds * 1000 + fullDateStamp.nanoseconds / 1000
                    val sdf = SimpleDateFormat("MM/dd/yyyy", Locale.getDefault())
                    val fullDate = sdf.format(Date(dateInMillis)).toString()
                    
                    val type = document.data?.get("type").toString()

                    // 同时携带发送方和接收方的消息ID,用于后续删除操作
                    val chatData = SingleChatData(coMsg, coId, senderMsgId, msgTime, chatDay, fullDate, type, receiverMsgId)
                    messages.add(chatData)
                }
                recyclerView.post {
                    recyclerView.smoothScrollToPosition(adapter.itemCount - 1)
                }
                adapter.notifyDataSetChanged()
            }
        }
}

// 页面销毁时移除监听,防止内存泄漏
override fun onDestroy() {
    super.onDestroy()
    senderMsgListener?.remove()
}

额外优化建议

  • 消息存储规范:发送消息时,生成一个全局唯一的msgUid,同时存入发送方和接收方房间的消息文档中,删除时通过msgUid批量删除两边的消息,无需单独存储对方房间的消息ID。
  • 避免嵌套监听:嵌套监听会导致逻辑混乱和性能损耗,如需多数据源关联,建议使用批量获取(get())而非实时监听,或拆分监听逻辑。
  • 数据去重兜底:如果必须拉取多源数据,添加消息时通过msgUid等唯一标识做去重判断,避免重复添加。

内容的提问来源于stack exchange,提问作者Mahmoud Nabil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 18:07:15