问题
I am building a chat application, and on the conversations/chats list, the last message of a chat is retrieved in order to show it just like in any other messenger app.
However, when the chat is opened, all messages of the chat are retrieved using messagesDbRef.orderByChild("time").addChildEventListener(...) and the onChildAdded callback is instantly called for the last message of the chat (the one retrieved on the chats list) which has the highest value for the time field, while all other messages are then retrieved from the database in the ascending order of the time field values.
In the example of messages labeled from 1 to 5, this causes them to be added to the RecyclerView in the order [5, 1, 2, 3, 4], with 5 being the last message.
However, when the chat is closed and opened again, the order is correct [1, 2, 3, 4, 5].
How can I fix this? Is there a way to force reload all messages when the chat is opened?
EDIT:
Here is a minimal working example that reproduces the problem:
Data in realtime database:
testing: {
abc123: {
name: "first",
time: 100
},
abc456: {
name: "second",
time: 200
},
abc789: {
name: "third",
time: 300
}
}
Code:
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);
}
...
});
This produces the output:
I/Testing: retrived child third with time 300
I/Testing: name: third, time: 300
I/Testing: name: first, time: 100
I/Testing: name: second, time: 200
However, I noticed that if I use addListenerForSingleValueEvent instead of addValueEventListener, the problem is gone and the order is correct. I guess I just have a listener open somewhere in my application.
In any case, I don't think that the cached value should interfere with the order of retrieval later on.
回答1:
The behavior is caused by the fact that Firebase already has one of the children in memory indeed. I explained it recently in an answer to Firebase query: Why is child_added called before the value query in the following code?.
But the order is maintained, as long as you use all the information that Firebase gives you. To show this I added some additional logging to your code:
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());
}
});
}
So this now logs:
- the key for each child snapshot
- the previous key (called
sin your code) that is passed - it's also logging each event, although in this test we only use
onChildAdded
When you run this code it logs:
01-07 10:10:23.859 3706-3706/? I/Testing: retrieved child third with time 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: key abc123 name first, time 100 previousKey null
01-07 10:10:23.933 3706-3706/? I/Testing: ChildAdded: key abc456 name second, time 200 previousKey abc123
Using the previousKey/s argument is key in this case, which becomes clear if we replay how you build a UI/list out of this information. I recommend that you refer to the [reference documentation for onChildAdded() before going through these steps](https://firebase.google.com/docs/reference/android/com/google/firebase/database/ChildEventListener.html#onChildAdded(com.google.firebase.database.DataSnapshot, java.lang.String)):
- You start with an empty list.
- You receive the child
abc789, which you add to the list as its only element:[abc789] - You receive the child
abc123. Since it doesn't have a previous key, you add it to the start of the list:[abc123, abc789] - You receive the child
abc456, with previousKeyabc123. When we insert this afterabc123we get the final list:[abc123, abc456, abc789]
So while the order in which the onChildAdded calls happen is indeed somewhat surprising, the information passed into them allows you to build the correct UI/list for the children.
来源:https://stackoverflow.com/questions/48139076/firebase-caching-ruins-order-of-retrieved-children