火碱缓存破坏检索到的孩子的顺序



我正在构建一个聊天应用程序,在对话/聊天列表中,检索聊天的最后一条消息,以便像在任何其他信使应用程序中一样显示它。

但是,当打开聊天时,将使用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());
}
});
}

所以现在记录:

  1. 每个子快照的键
  2. 传递的上一个键(在代码中称为s)
  3. 它还记录每个事件,尽管在此测试中我们只使用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)):

  1. 您从一个空列表开始。
  2. 您会收到子abc789,并将其作为其唯一元素添加到列表中:[abc789]
  3. 您收到子abc123。由于它没有上一个键,因此将其添加到列表的开头[abc123, abc789]
  4. 您会收到子abc456,并带有上一个密钥abc123。当我们在abc123之后插入它时,我们得到最终列表:[abc123, abc456, abc789]

因此,虽然onChildAdded调用发生的顺序确实有些令人惊讶,但传递到它们的信息允许您为子级构建正确的 UI/列表。

最新更新