4

I am using Firebase and I have had no problems getting data in alphabetical order until recently. I never used queries, I always just used snapshots of data and sorted through them one-by-one. Recently, the data has not been always coming in alphabetical order in the snapVal. How do I make it so I get a snapVal of data sorted alphabetically, like it is in the snapshot from the database?

Real Example: there are 4 messages, id1-id4 (in that order). They contiain the message "1"-"4". The snapshot comes looking correct. But the snapVal (snapshot.value) looks like this:

["id2": {
    DATE = "10/20/16";
    "FIRST_NAME" = first;
    ID = userID;
    "LAST_NAME" = last;
    MESSAGE = 2;
    TIME = "8:12 PM";
}, "id4": {
    DATE = "10/20/16";
    "FIRST_NAME" = first;
    ID = userID;
    "LAST_NAME" = last;
    MESSAGE = 4;
    TIME = "8:12 PM";
}, "id1": {
    DATE = "10/20/16";
    "FIRST_NAME" = first;
    ID = userID;
    "LAST_NAME" = last;
    MESSAGE = 1;
    TIME = "8:12 PM";
}, "id3": {
    DATE = "10/20/16";
    "FIRST_NAME" = first;
    ID = userID;
    "LAST_NAME" = last;
    MESSAGE = 3;
    TIME = "8:12 PM";
}]

What the snapshot looks like:

["id1": {
    DATE = "10/20/16";
    "FIRST_NAME" = first;
    ID = userID;
    "LAST_NAME" = last;
    MESSAGE = 1;
    TIME = "8:12 PM";
}, "id2": {
    DATE = "10/20/16";
    "FIRST_NAME" = first;
    ID = userID;
    "LAST_NAME" = last;
    MESSAGE = 2;
    TIME = "8:12 PM";
}, "id3": {
    DATE = "10/20/16";
    "FIRST_NAME" = first;
    ID = userID;
    "LAST_NAME" = last;
    MESSAGE = 3;
    TIME = "8:12 PM";
}, "id4": {
    DATE = "10/20/16";
    "FIRST_NAME" = first;
    ID = userID;
    "LAST_NAME" = last;
    MESSAGE = 4;
    TIME = "8:12 PM";
}]

To get the snapVal, I use this:

if let snapVal = snapshot.value as? [String: AnyObject] {
     // Comes out of order..
}

To clarify:

Snapshot (this ends up coming out correct):

Snap (CHAT) {
    id1 =     {
        DATE = "10/20/16";
        "FIRST_NAME" = first;
        ID = userID;
        "LAST_NAME" = last;
        MESSAGE = 1;
        TIME = "8:12 PM";
    };
    id2 =     {
        DATE = "10/20/16";
        "FIRST_NAME" = first;
        ID = userID;
        "LAST_NAME" = last;
        MESSAGE = 2;
        TIME = "8:12 PM";
    };
    id3 =     {
        DATE = "10/20/16";
        "FIRST_NAME" = first;
        ID = userID;
        "LAST_NAME" = last;
        MESSAGE = 3;
        TIME = "8:12 PM";
    };
    id4 =     {
        DATE = "10/20/16";
        "FIRST_NAME" = first;
        ID = userID;
        "LAST_NAME" = last;
        MESSAGE = 4;
        TIME = "8:12 PM";
    };
}

This is the output for print(snapVal.keys) inside if let snapVal = snapshot.value as? [String: AnyObject]:

LazyMapCollection<Dictionary<String, AnyObject>, String>(_base: ["id2": {
    DATE = "10/20/16";
    "FIRST_NAME" = first;
    ID = userID;
    "LAST_NAME" = last;
    MESSAGE = 2;
    TIME = "8:12 PM";
}, "id4": {
    DATE = "10/20/16";
    "FIRST_NAME" = first;
    ID = userID;
    "LAST_NAME" = last;
    MESSAGE = 4;
    TIME = "8:12 PM";
}, "id1": {
    DATE = "10/20/16";
    "FIRST_NAME" = first;
    ID = userID;
    "LAST_NAME" = last;
    MESSAGE = 1;
    TIME = "8:12 PM";
}, "id3": {
    DATE = "10/20/16";
    "FIRST_NAME" = first;
    ID = userID;
    "LAST_NAME" = last;
    MESSAGE = 3;
    TIME = "8:12 PM";
}], _transform: (Function))

My Code:

  self.firebase.child("Chats").child(chatID).queryOrderedByKey().observeSingleEvent(of: .value, with: { (snapshot) in
                          print(snapshot)
                        if let snapVal = snapshot.value as? [String: AnyObject] {

                            print(snapVal)

                            for c in snapVal {
                                print("checking Message as child")
                                let message = c.value["MESSAGE"] as? String

                                let fn = c.value["FIRST_NAME"] as? String
                                let ln = c.value["LAST_NAME"] as? String

                                let USER_ID = c.value["ID"] as? String

                                if let userID = USER_ID {
                                    if let msg = message {
                                        if let firstName = fn {
                                            if let lastName = ln {
                                                let username = "\(firstName) \(lastName)"
                                                self.addMessage(userID, text: msg, name: username)

                                                print("Message added! \nMessage Info:")
                                                print("User ID: \(userID)")
                                                print("text: \(msg)")
                                                print("Username: \(username)")
                                            } else {
                                                print("LN did not pass")
                                            }
                                        } else {
                                            print("FN did not pass")
                                        }
                                    } else {
                                        print("Msg did not pass")
                                    }
                                } else {
                                    print("User ID did not pass")
                                }
                            }
                          }  
})
Ryan Cocuzzo
  • 3,109
  • 7
  • 35
  • 64
  • Without seeing the [minimal code + data that reproduces the problem](http://stackoverflow.com/help/mcve) it will be difficult to help. – Frank van Puffelen Oct 21 '16 at 00:24
  • @FrankvanPuffelen My code is correct. The issue is that my data snapshots are coming in with data pieces out of order. – Ryan Cocuzzo Oct 21 '16 at 00:30
  • @FrankvanPuffelen I updated the question to give you a better understanding of my problem – Ryan Cocuzzo Oct 21 '16 at 00:33
  • @FrankvanPuffelen I updated the question again. The snapshot is coming correct but the snapshot.value is coming out of order, why is this? – Ryan Cocuzzo Oct 21 '16 at 00:38
  • Please update to also include the code that actually retrieves the snapshot. An instead of saying "out of order", show the order they appear in (a `print(snapshot.key)` statement is typically quite effective for this) and the order you'd expect them in. – Frank van Puffelen Oct 21 '16 at 01:28
  • @FrankvanPuffelen I updated the question and now I believe it should help you see the issue – Ryan Cocuzzo Oct 21 '16 at 01:45
  • @FrankvanPuffelen I updated the question and now I believe it should help you see the issue. It seems so definitive, I am beginning to believe it is an issue with the Firebase native iOS integration code. – Ryan Cocuzzo Oct 21 '16 at 02:13

4 Answers4

7

Since you haven't shared the necessary code, I'll assume you're doing something along these lines:

ref!.queryOrdered(byChild: "text").observe(.value, with: { (snapshot) in
    print("\(snapshot.value)")
})

When you execute a query on a Firebase location, the data is returned with information about the order of the items according to the query. When you observe a value event, the snapshot contains the keys, the values and the order of the children.

But when you convert request the snapshot.value property, all information has to be converted into a dictionary. The keys and the values of each child survive this conversion, but the information on ordering is lost.

For this reason, you'll have to use the children property of the snapshot to iterate over the children in the correct order:

ref!.queryOrdered(byChild: "text").observe(.value, with: { snapshot in
    for child in snapshot.children {
        print("child \(child)")
    }
})
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Please check out my code, I updated the question to include it at the end. Notice I ordered the query by key and it is still coming out of order in the `snapshot.value`. I also believe I need to use the NSDictionary as I did (in `String: AnyObject`) to properly access values within children. – Ryan Cocuzzo Oct 22 '16 at 17:20
  • By the way, you have been very helpful in my questions in the past and present, I just want to say thank you for that. @FrankVanPuffelen – Ryan Cocuzzo Oct 22 '16 at 17:22
3

Solution: After very extensive searching and attempting, the problem still persisted that once the snapshot was converted to a snapVal (snapshot.value), the order often rearranged. My (working) solution:

for child in snapshot.children {
let child = child as! FIRDataSnapshot
 if let childVal = child.value as? [String: AnyObject] {
    let childMessage = childVal["MESSAGE"] as! String
    // more code for each child. This child might be a post of a sort, which you can access properties of in a way like I did in the line above
 }
}

Process:

  1. Loop through each child in snapshot

  2. Convert the child to a FIRDataSnapshot so it is not an element

  3. Get the value of the particular child to access properties from

  4. Add in the respective code for the child following NSDictionary principles.

Why this solution is solid

Receiving snapshots in the proper order is very simple. The issue I faced was getting data in the correct order when I got the snapshot.value. This solution fixes that because the values of each child are only accessed when looping through the children of snapshot, which is sorted. This leaves the order of children still in the control of the snapshot.

I also like the snapshot.value approach by using [String: AnyObject] because it is very close to the old functionality of Firebase implementation in Swift: Simple and very clean. I actually think that using NSDictionary in this way is really a way to save time in the long run because it is not verbose in any way.

Ryan Cocuzzo
  • 3,109
  • 7
  • 35
  • 64
2

From my experience with Firebase you cannot guarantee the order that the data is being returned in the snapshot value. Firebase provides a couple of functions that you can add to your reference query though to order and sort the data based on key queryOrderedByKey, value queryOrderedByValue, or child queryOrderedByChild.

From your description it sounds like you may want to use queryOrderedByChild to sort you snapshots correctly.

Here's the documentation on those functions and scroll down to the sort data section. https://firebase.google.com/docs/database/ios/lists-of-data

Donny
  • 76
  • 2
  • I am very familiar with snapshots, but not with the queries, which would make me have to re-write a lot of code. Also, the snapshots are returning in the right order. There is just some issue occurring between the snapshot and the snapVal, where the snapVal ends up in a different order – Ryan Cocuzzo Oct 21 '16 at 03:04
  • That is the same behavior that I have experienced as well. Not sure why they are different. I have only used the sorting functions a couple of times myself. Mostly the data I've dealt with is small enough where I create an object/struct from the snapshot value, store it in an array, and then run map or flatMap over it to sort it how I want. Probably not the best way but has worked for my few use cases so far. – Donny Oct 21 '16 at 03:20
  • I added this and it did not help me unfortunately. Please check out my code to give yourself an understanding of my approach. – Ryan Cocuzzo Oct 22 '16 at 17:18
0

The problem as you can see is not that Firebase is not giving you the response ordered by as requested, the problem is that you are parsing the response into an Dictionary and Dictionaries are no ordered list. The simplest way to fix it is to order your dictionary using something like this:

self.messages.sortInPlace({ ($0.date.compare($1.date) == NSComparisonResult.OrderedAscending)}) 

Or a even better way is to add the result directly into an array. You can find more about this problem in this question in my course, there is a long discussion about it there (https://www.udemy.com/firebase/learn/v4/questions/1341056).

Let me know if that helps you.

Icaro
  • 14,585
  • 6
  • 60
  • 75