Firebase缓存破坏了检索孩子的顺序

时间:2018-01-07 16:16:31

标签: android firebase firebase-realtime-database

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

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

            ...
});

这会产生输出:

  

我/测试:在时间300的情况下重新获得第三名儿童

     

我/测试:名称:第三,时间:300

     

I / Testing:name:first,time:100

     

我/测试:名称:秒,时间:200

但是,我注意到如果我使用addListenerForSingleValueEvent代替addValueEventListener,问题就会消失,订单也是正确的。我想我的应用程序中只有一个监听器可以打开。

在任何情况下,我都认为缓存的值不会影响以后的检索顺序。

1 个答案:

答案 0 :(得分:1)

这种行为是由于Firebase确实已经拥有内存中的一个子节点。我最近在对Firebase query: Why is child_added called before the value query in the following code?的回答中解释了这一点。

但只要您使用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
  4. 运行此代码时,它会记录:

      

    01-07 10:10:23.859 3706-3706 /?我/测试:在时间300

    中检索到第三个孩子      

    01-07 10:10:23.869 3706-3706 /? I / Testing:ChildAdded:key abc789 name third,time 300 previousKey null

         

    01-07 10:10:23.932 3706-3706 /? I / Testing:ChildAdded:首先输入密钥abc123,时间100 previousKey null

         

    01-07 10:10:23.933 3706-3706 /?我/测试:ChildAdded:键abc456名称第二,时间200 previousKey abc123

    在这种情况下,使用previousKey / s参数是关键,如果我们重播您如何使用此信息构建UI /列表,这一点就变得清晰了。我建议您在完成这些步骤之前参考[{1}}的参考文档](https://firebase.google.com/docs/reference/android/com/google/firebase/database/ChildEventListener.html#onChildAdded(com.google.firebase.database.DataSnapshot,java.lang.String)):

    1. 你从一个空列表开始。
    2. 您收到的孩子onChildAdded(),您将其作为唯一元素添加到列表中:abc789
    3. 您收到了孩子[abc789]。由于它没有以前的密钥,您可以将其添加到列表的开始abc123
    4. 您收到了孩子[abc123, abc789],其中包含previousKey abc456。当我们在abc123之后插入此内容时,我们会得到最终列表:abc123
    5. 因此,虽然[abc123, abc456, abc789]调用发生的顺序确实有点令人惊讶,但传递给它们的信息允许您为子项构建正确的UI /列表。