3

I am getting a snapshot of the data from my Firebase database to retrieve a users list, but the order of the keys being returned is different depending on the Android version/ device being used.

For demonstrative purposes I have shortened the method, but it is essentially as follows:

public void getUsers(){
    Firebase ref = new Firebase("https://myFirebaseID.firebaseio.com");
    final Firebase userRef = ref.child("users");

    userRef.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot snapshot) {
           snapshot.toString();
        }                                                           
    });
}

It is the data I get from calling toString() on the snapshot object (snapshot.toString()) that changes order.

I have tried it on 4 devices. The 2 running Lollipop (Nexus 7 5.1.1 & Galaxy s4 5.01) return the data in the same order. And the 2 two other devices (HTC Sensation 4.0.3 and Motorola G2 4.4.4) return the data in the same order (but a different order to devices with Lollipop).

There is no difference in the code used, and the data in the database was completely unchanged at the times when I retrieved the snapshots.

Here is the data order on the 4.4.4 and 4.0.3 devices:

DataSnapshot { 
  key = users, 
  value = {
    114585619420240714499={
      **userIDOfCUser**=114585619420240714499,
      **NameOfCUser**=testName,
      **EmailOfCUser**=testerfireapp@gmail.com,
      **friends**={
        103902248954972338254={
          **userIDOfFriend**=103902248954972338254, 
          **NameOfFriend**=testName2 
        }
      }
    }  

Here is the data order on the 5.1.1 and 5.01 devices:

DataSnapshot { 
  key = users, 
  value = {
    114585619420240714499={
      **NameOfCUser**=testName, 
      **userIDOfCUser**=114585619420240714499, 
      **friends**={
        103902248954972338254={
          **NameOfFriend**=testName2 ,
          **userIDOfFriend**=103902248954972338254
        }
      }, 
      **EmailOfCUser**= testerfireapp@gmail.com
    }
  }
}

Why is the data being delivered in different orders depending on the android version/device being used? is there another difference I am unaware of?

Edit: When iterating through the snapshot as follows, the different ordering of the keys still persists accross different versions of Android:

public void getUsers2(){

  Firebase ref = new Firebase("https://myFirebaseID.firebaseio.com");
  final Firebase userRef = ref.child("users");
  userRef.addListenerForSingleValueEvent(new ValueEventListener() {

    @Override
    public void onDataChange(DataSnapshot snapshot) {
        for (DataSnapshot keys : snapshot.getChildren()) {
            //String to temporarily store the whole child of the inidividual user's DB node ----> It still produces different order of the keys 
            String tempKey = keys.getValue().toString();

            //The problem persists if I code it like this as well. 
            String tempKey2 = snapshot.child(keys.getKey()).getValue().toString();

        }
    }
  });
}
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Micah Simmons
  • 2,078
  • 2
  • 20
  • 38
  • Hi Micah. I indented the JSON in your question, because it is impossible for me to make sense of it otherwise. Can you double check if I broke something? – Frank van Puffelen Aug 04 '15 at 14:30
  • 1
    Hi Frank. Thank you very much for doing that and the edit. I’ll be sure to format it like that myself the next time I post on here! I just wish the android studio console would be kind enough to do the same thing lol. I double checked it and all the keys and values are in the correct order with the same values. Thanks. – Micah Simmons Aug 04 '15 at 14:37
  • 1
    If you iterate through the `snapshot` (ie `for (DataSnapshot child : snapshot.getChildren()`) instead of calling `toString()`, does the order still change? – Reed Aug 04 '15 at 14:45
  • I just double checked using your method as I was using a slightly different way of iterating through them, but indeed yes, it still changes the order of the keys. – Micah Simmons Aug 04 '15 at 14:58
  • As said below: there is no guarantee of any order of children within JSON. So it may be different on different Android versions. Where you do `String tempKey = keys.getValue().toString();` you convert an entire user (including the embedded `friends`) to JSON. I might be wrong, but it's hard to tell without a complete reproduction of you problem (the Android `toString()` output is also difficult to parse). Can you set up a separate Firebase database with actual data that we can look at and post a complete, minimal code sample of what you're trying to accomplish. – Frank van Puffelen Aug 04 '15 at 19:17
  • Yeah, providing the exact data in question would be helpful (I assume ** NameOfCUser ** is a placeholder, and not your actual data). It would also be helpful to know if there are any priorities. You can use getValue(true) to get the "export" version of the data, which will include priorities. – Michael Lehenbauer Aug 04 '15 at 20:23
  • Thanks Frank. As you say there is no guarantee of the order, though, so far after my attempts to retrieve the data it has come in the same order each time, it has just varied between which version of Android I have been using, which is quite interesting. I do indeed convert an entire user and all of the keys it contains to a string, and then extract information from it using substring() methods. I will get onto creating a separate Firebase as you suggested and post back here asap. Thanks for your help! – Micah Simmons Aug 04 '15 at 20:42
  • Thanks Michael, my current data structure is still in a preliminary stage, but I plan to use *NameOfCUser* in the real app, the ‘C’ stands for ‘current’. Though all the values assigned to the keys in my post here are just dummy values. I haven’t begun using priorities yet, though I will if you think that it will be beneficial to me, I will research them more in the meantime. As I said to Frank, I will post back on here once I have created another Firebase with the minimal code required to do what I want to accomplish and hopefully that will bring some more clarity. – Micah Simmons Aug 04 '15 at 20:49
  • I just looked at this again and I think you misunderstood about using getChildren(). Instead of "keys.getValue().toString();" you should be calling "keys.getValue().getChildren()" and enumerating that. Any time you call toString() or getValue() the order of the children is undefined and will depend on the android runtime. In general, you should never try to parse the output of toString(). – Michael Lehenbauer Aug 04 '15 at 21:05
  • Thanks for the info!When I call **keys.getValue()**, the getChildren() method isn’t available to be called on it.Are you 100% it’s in that order? When that didn’t work I created another nested enhanced for loop like **(-> for (DataSnapshot key2 : keys.getChildren()){ })**this made them more similar but the different ordering was still there. Was that the type of set up you meant? This maybe a stupid question but I have to ask. Without using toString() or getValue() and then using the substring() method to get the value of a key, what is the best way to get the values of keys from a snapshot? – Micah Simmons Aug 04 '15 at 22:34

1 Answers1

3

The documentation for Firebase's REST API contains this warning:

When using the REST API, the filtered results are returned in an undefined order since JSON interpreters don't enforce any ordering. If the order of your data is important you should sort the results in your application after they are returned from Firebase.

In other words: the properties in a JSON object can be in any order. A JSON parser is free to re-arrange them as it sees fit.

You are not using the REST API, but the Firebase Android SDK. All the Firebase SDKs take care if hiding such re-ordering from you or (when hiding it is not possible) telling you explicitly what the order is (e.g. the previousChildName argument to the onChildAdded callback)

But from the data you're showing it seems like you're parsing the output from dataSnapshot.toString(). While this is totally valid, it does mean that you're choosing to handle the ordering yourself. In general it's best to stick to using the methods of the Firebase Android SDK, since they handle things like ordering for you:

public void onDataChange(DataSnapshot snapshot) {
  for (DataSnapshot friendSnapshot: snapshot.getChildren()) {
    System.out.println(friendSnapshot.child("NameOfFriend").getValue());
  }
} 

Update

From your update it seems like you have a query on users and then want to also loop over their friends. With the Firebase Android API you'd do that with:

Firebase ref = new Firebase("https://myFirebaseID.firebaseio.com");
final Firebase userRef = ref.child("users");
userRef.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot userSnapshot) {
    DataSnapshot friendsSnapshot = userSnapshot.child("friends");
    for (DataSnapshot friendSnapshot : friendsSnapshot.getChildren()) {
      System.out.println(friendSnapshot.child("NameOfFriend").getValue());
    }
  }
});
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thanks for the insight Frank. I think I have understood you. Though my attempt to be brief in asking my question has made it look as if I was calling toString() on the snapshot and then getting data from there in my actual app, but I just put that in the question to simplify my code. I have edited the question and added a meatier version of my method. I also started using getValue() instead of just toString() on my equivalent 'friendSnapshot' iterator. But assuming I have implemented it correctly? The same problem of the different orders still persists across the different versions of android. – Micah Simmons Aug 04 '15 at 16:13
  • Thanks! That makes sense, currently my code is quite verbose in terms of how much data I’m retrieving for my users list, as I am taking all of the keys from a user node, then extracting the necessary data using substring methods, but in this instance, the friends of a user aren't needed. I considered the extra data necessary baggage to get the other data (name, userID etc). With populating the friends list of the currently signed in user, I’m using the child() method to get the friends node of the user and then retrieving the data from Firebase. So far it has retrieved much less unwanted data – Micah Simmons Aug 04 '15 at 21:11
  • You should only store the friends list under a user's profile, if you will *always* need the friends list when you load the user's profile. Otherwise you should have two separate nodes for each user: `/users/$uid` where you store their profile data and `/users_friends/$uid` where you store the friends. Reducing bandwidth usage is one of the many reasons Firebase recommends to avoid building nests: https://www.firebase.com/docs/web/guide/structuring-data.html#section-denormalizing-data – Frank van Puffelen Aug 04 '15 at 21:15
  • That's really good advice. I'm still in the preliminary stages of building up the functionality of the app, and at this stage I doubt I will always need access to the friend's, so as you suggest I will flatten out the database more by doing this. Thanks Frank. – Micah Simmons Aug 04 '15 at 22:49