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

Firestore消息监听中date字段首次返回null的问题求助

问题原因与解决方案

问题根源

Firestore 使用 @ServerTimestamp 时,客户端写入数据会触发两次快照更新:

  • 第一次是本地写入确认:此时服务器时间还未生成,date 字段为 null
  • 第二次是服务器同步完成:此时服务器已生成时间戳,date 字段才有有效值

你的消息监听器在第一次触发时直接使用了 null 的 date,导致后续排序或逻辑出错;而重新打开 Fragment 时,读取的是服务器已同步完成的完整数据,所以显示正常。

解决方案

1. 处理 date 为 null 的情况,避免空指针或排序异常

修改排序逻辑,将 date 为 null 的消息临时放在列表末尾,等服务器同步后自动修正:

// 替换原排序代码
messages.sortBy { it.date ?: Date(Long.MAX_VALUE) }

或者在创建 Message 时,给 date 一个临时默认值(比如当前客户端时间),等服务器同步后会自动覆盖:

var date = doc.getTimestamp(KEY_MESSAGE_DATE)?.toDate() ?: Date()

2. 优化监听器的消息去重逻辑

因为同一条消息会触发两次 ADDED 事件,原有的 if (message !in messages) 可能无法正确去重(两次的 date 不同)。建议改用 message.id 作为唯一标识判断:

// 替换原消息添加逻辑
val existingIndex = messages.indexOfFirst { it.id == id }
if (existingIndex == -1) {
    messages.add(message)
} else {
    // 如果是第二次更新(date 不为 null),替换原消息
    if (date != null) {
        messages[existingIndex] = message
    }
}

3. 确认 Message 类的 @ServerTimestamp 注解正确

确保实体类中对应的字段类型为可空的时间类型且注解正确:

import com.google.firebase.firestore.ServerTimestamp
import java.util.Date

data class Message(
    val text: String,
    val id: String,
    val receiverId: String,
    @ServerTimestamp val date: Date?,
    val isChecked: Boolean,
    val type: String
)

修改后的完整监听器代码示例

database
    .collection(KEY_COLLECTION_USERS)
    .document(preferenceManager.getString(KEY_USER_ID)!!)
    .collection(KEY_COLLECTION_CHAT)
    .addSnapshotListener { value, error ->
        if (error != null || value == null) return@addSnapshotListener

        for (documentChange in value.documentChanges) {
            if (documentChange.type == DocumentChange.Type.ADDED) {
                val doc = documentChange.document
                if (doc.getString(KEY_MESSAGE_RECEIVER_ID) == receiverUser.uid) {
                    val text = doc.getString(KEY_MESSAGE).toString()
                    val id = doc.getString(KEY_MESSAGE_ID).toString()
                    val receiverId = doc.getString(KEY_MESSAGE_RECEIVER_ID).toString()
                    val date = doc.getTimestamp(KEY_MESSAGE_DATE)?.toDate() ?: Date()
                    val isChecked = doc.getBoolean(KEY_MESSAGE_ISCHECKED)!!
                    val type = doc.getString(KEY_TYPE_MESSAGE).toString()
                    val message = Message(text, id, receiverId, date, isChecked, type)

                    // 按ID去重,存在则替换(处理第二次更新)
                    val existingIndex = messages.indexOfFirst { it.id == message.id }
                    if (existingIndex == -1) {
                        messages.add(message)
                    } else {
                        messages[existingIndex] = message
                    }
                }
            }
        }

        messages.sortBy { it.date }
        val messagesTest = ArrayList(messages)
        userChatAdpater.updateList(messagesTest)
        if (messages.isNotEmpty()) {
            mBinding.rvUserChat.smoothScrollToPosition(messages.size - 1)
        }
    }

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 14:15:31