Firebase Kotlin中获取ServerTimestamp生成的Date对象及消息排序问题
Firebase自动生成时间戳的获取与消息排序修复
问题场景
定义了消息数据类,通过@ServerTimestamp注解让Firebase自动生成消息时间戳,添加消息时将date设为null,但在监听获取消息时,要么无法将时间戳转为Date对象,要么出现空指针异常,导致无法正常显示时间和排序消息列表。
消息数据类
data class Message( var message: String, var id: String, var receiverID: String, @ServerTimestamp var date: Date? = null, var isChecked: Boolean, var type: String )
添加消息代码
val message = Message(text, id, receiverUser.uid, null, false, MESSAGE_TYPE_TEXT) database .collection(KEY_COLLECTION_USERS) .document(preferenceManager.getString(KEY_USER_ID)!!) .collection(KEY_COLLECTION_CHAT) .add(message) mBinding.typeMessageField.setText(null)
原监听代码(存在问题)
database .collection(KEY_COLLECTION_USERS) .document(preferenceManager.getString(KEY_USER_ID)!!) .collection(KEY_COLLECTION_CHAT) .addSnapshotListener { value, error -> var messages_test = ArrayList(messages) if (error != null) { return@addSnapshotListener } else { if (value == null) { return@addSnapshotListener } else { for (documentChange in value.documentChanges) { if (documentChange.type == DocumentChange.Type.ADDED) { var doc = documentChange.document if (doc.getString(KEY_MESSAGE_RECEIVER_ID) == receiverUser.uid) { var text = doc.getString(KEY_MESSAGE).toString() var id = doc.getString(KEY_MESSAGE_ID).toString() var receiverId = doc.getString(KEY_MESSAGE_RECEIVER_ID).toString() var date = doc.getTimestamp(KEY_MESSAGE_DATE)?.toDate() var isChecked = doc.getBoolean(KEY_MESSAGE_ISCHECKED)!! var type = doc.getString(KEY_TYPE_MESSAGE).toString() var message = Message(text, id, receiverId, date, isChecked, type) if (message !in messages) { messages.add(message) } } } } messages.sortBy { it -> it.date } var messages_test = ArrayList(messages) userChatAdpater.updateList(messages_test) if (messages.size != 0) { mBinding.rvUserChat.smoothScrollToPosition(messages.size - 1) } } } }
问题分析
- 空指针风险:直接对
getString()返回值调用toString(),或用!!强制解包布尔值,当字段不存在时会触发空指针。 - 字段映射不匹配:若
KEY_MESSAGE_DATE与数据类中date字段的Firestore存储键名不一致,会导致获取到null时间戳。 - 排序逻辑漏洞:
sortBy { it.date }未处理date为null的情况,可能导致排序异常。 - 低效去重:通过
message !in messages判断重复,依赖data class的全字段比较,效率低且可能因date值差异误判。
修复方案
1. 安全获取字段值
改用安全调用+默认值,避免空指针:
val text = doc.getString(KEY_MESSAGE) ?: "" val id = doc.getString(KEY_MESSAGE_ID) ?: "" val receiverId = doc.getString(KEY_MESSAGE_RECEIVER_ID) ?: "" val date = doc.getTimestamp(KEY_MESSAGE_DATE)?.toDate() val isChecked = doc.getBoolean(KEY_MESSAGE_ISCHECKED) ?: false val type = doc.getString(KEY_TYPE_MESSAGE) ?: MESSAGE_TYPE_TEXT
2. 统一字段映射
在数据类中明确指定Firestore字段名,避免驼峰转蛇形的自动映射误差:
data class Message( var message: String, var id: String, var receiverID: String, @ServerTimestamp @FieldName(KEY_MESSAGE_DATE) // 明确指定数据库字段名 var date: Date? = null, var isChecked: Boolean, var type: String )
3. 安全排序
处理date为null的极端情况,将这类消息排在末尾:
messages.sortBy { it.date ?: Date(0) }
4. 高效去重
通过消息唯一ID判断重复,比全字段比较更可靠高效:
val existingMessage = messages.find { it.id == message.id } if (existingMessage == null) { messages.add(message) }
修复后的完整监听代码
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) ?: "" val id = doc.getString(KEY_MESSAGE_ID) ?: "" val receiverId = doc.getString(KEY_MESSAGE_RECEIVER_ID) ?: "" val date = doc.getTimestamp(KEY_MESSAGE_DATE)?.toDate() val isChecked = doc.getBoolean(KEY_MESSAGE_ISCHECKED) ?: false val type = doc.getString(KEY_TYPE_MESSAGE) ?: MESSAGE_TYPE_TEXT val message = Message(text, id, receiverId, date, isChecked, type) val existingMessage = messages.find { it.id == message.id } if (existingMessage == null) { messages.add(message) } } } } messages.sortBy { it.date ?: Date(0) } userChatAdpater.updateList(ArrayList(messages)) if (messages.isNotEmpty()) { mBinding.rvUserChat.smoothScrollToPosition(messages.size - 1) } }
内容的提问来源于stack exchange,提问作者Danil
相关产品推荐
相关产品推荐

