我正在构建一个聊天应用程序,在对话/聊天列表中,检索聊天的最后一条消息,以便像在任何其他信使应用程序中一样显示它。
但是,当打开聊天时,将使用messagesDbRef.orderByChild("time").addChildEventListener(...)
检索聊天的所有消息,并且会立即为聊天的最后一条消息(聊天列表中检索的消息)调用onChildAdded
回调,该消息对time
字段具有最高值,而所有其他消息随后按time
字段值的升序从数据库中检索。
在标记为从 1 到 5 的消息示例中,这会导致它们按顺序 [5, 1, 2, 3, 4] 添加到 RecyclerView,其中 5 是最后一条消息。
但是,当聊天关闭并再次打开时,顺序是正确的[1,2,3,4,5]。
我该如何解决这个问题?有没有办法在打开聊天时强制重新加载所有消息?
编辑:
下面是重现该问题的最小工作示例:
实时数据库中的数据:
testing: {
abc123: {
name: "first",
time: 100
},
abc456: {
name: "second",
time: 200
},
abc789: {
name: "third",
time: 300
}
}
法典:
final DatabaseReference ref = FirebaseDatabase.getInstance().getReference("testing");
ref.child("abc789").addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
Stuff lastStuff = dataSnapshot.getValue(Stuff.class);
Log.i("Testing", "retrived child " + lastStuff.name + " with time " + lastStuff.time);
ref.orderByChild("time").addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
Stuff stuff = dataSnapshot.getValue(Stuff.class);
Log.i("Testing", "name: " + stuff.name + ", time: " + stuff.time);
}
...
});
这将产生输出:
I/测试:时间 300 的第三名
I/测试:名称:第三,时间:300
I/测试:名称:第一个,时间:100
I/测试:名称:秒,时间:200
但是,我注意到如果我使用addListenerForSingleValueEvent
而不是addValueEventListener
,问题就消失了,顺序是正确的。我想我只是在我的应用程序中的某个地方打开了一个侦听器。
无论如何,我认为缓存的值不应该干扰以后的检索顺序。
该行为是由于Firebase确实已经有一个孩子在内存中。我最近在回答Firebase查询时对此进行了解释:为什么在以下代码中在值查询之前调用child_added?
但是,只要您使用Firebase为您提供的所有信息,顺序就会保持不变。为了显示这一点,我在代码中添加了一些额外的日志记录:
ref.child("abc789").addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.i("Testing", "retrieved child " + dataSnapshot.child("name").getValue() + " with time " + dataSnapshot.child("time").getValue());
ref.orderByChild("time").addChildEventListener(new ChildEventListener() {
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
Log.i("Testing", "ChildAdded: key "+dataSnapshot.getKey()+" name " + dataSnapshot.child("name").getValue() + ", time " + dataSnapshot.child("time").getValue()+" previousKey "+s);
}
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
Log.i("Testing", "ChildChanged: key "+dataSnapshot.getKey()+" name " + dataSnapshot.child("name").getValue() + ", time " + dataSnapshot.child("time").getValue()+" previousKey "+s);
}
public void onChildRemoved(DataSnapshot dataSnapshot) {
Log.i("Testing", "ChildRemoved: key "+dataSnapshot.getKey()+" name " + dataSnapshot.child("name").getValue() + ", time " + dataSnapshot.child("time").getValue());
}
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
Log.i("Testing", "ChildMoved: key "+dataSnapshot.getKey()+" name " + dataSnapshot.child("name").getValue() + ", time " + dataSnapshot.child("time").getValue()+" previousKey "+s);
}
public void onCancelled(DatabaseError error) {
System.err.println("Listener was cancelled "+error.toString());
}
});
}
所以现在记录:
- 每个子快照的键
- 传递的上一个键(在代码中称为
s
) - 它还记录每个事件,尽管在此测试中我们只使用
onChildAdded
当您运行此代码时,它会记录:
01-07 10:10:23.859 3706-3706/?I/测试:检索到的子项第三,时间 300
01-07 10:10:23.869 3706-3706/?I/测试:子添加:键 abc789 名称第三,时间 300 上一页键空
01-07 10:10:23.932 3706-3706/?I/测试:子项添加:键 abc123 名称在前,时间 100 上一个键空
01-07 10:10:23.933 3706-3706/?I/测试:子添加:键 abc456 名称第二,时间 200 上一页键 abc123
在这种情况下,使用previousKey
/s
参数是关键,如果我们重播您如何根据这些信息构建 UI/列表,这一点就会变得清晰。我建议您在执行这些步骤之前参考[onChildAdded()
参考文档](https://firebase.google.com/docs/reference/android/com/google/firebase/database/ChildEventListener.html#onChildAdded(com.google.firebase.database.DataSnapshot,java.lang.String)):
- 您从一个空列表开始。
- 您会收到子
abc789
,并将其作为其唯一元素添加到列表中:[abc789]
- 您收到子
abc123
。由于它没有上一个键,因此将其添加到列表的开头:[abc123, abc789]
- 您会收到子
abc456
,并带有上一个密钥abc123
。当我们在abc123
之后插入它时,我们得到最终列表:[abc123, abc456, abc789]
因此,虽然onChildAdded
调用发生的顺序确实有些令人惊讶,但传递到它们的信息允许您为子级构建正确的 UI/列表。