7

For an iOS app I am working on, I need to fetch messages in descending order i.e the latest message comes first, followed by second newest message etc.

From looking at other SO answers and research it seems that the best approach for my situation is to create a negative timestamp and then persist that to the database as an extra property to messages.

I will then use queryOrderedByChild('negativeTimestamp') to fetch the messages in a observeSingleEvent and then have a childAdded observer to handle messages which are sent once initial calls are made.

In the firebase documentation it says I can get the server timestamp value from this snippet of code firebase.database.ServerValue.TIMESTAMP

How do I write this for Swift 3?

KENdi
  • 7,576
  • 2
  • 16
  • 31
Edward
  • 2,864
  • 2
  • 29
  • 39
  • I'm pretty sure that refs are added by timestamps in the first place, so I don't think any sorting is even necessary – Daniel van der Merwe Apr 04 '17 at 10:05
  • That is, unless you're doing some kind of join – Daniel van der Merwe Apr 04 '17 at 10:05
  • Another solution could be using `limitToLast` and then just sorting on the client side. E.g. `messages.sort { $0.timestamp > $1.timestamp }` – Daniel van der Merwe Apr 04 '17 at 10:10
  • they are but in ascending order - my issue with limitToLast is that you have to come up with an arbitrary number when who knows how many unread messages a user could have – Edward Apr 04 '17 at 10:39
  • I guess another solution could be to use an observeSingleEvent using .value to count how many messages there are and then do an initial fetch with that number using limitToLast – Edward Apr 04 '17 at 10:41
  • Everything you need to know is in the answer to this question [In Firebase, how can I query the most recent 10 child nodes?](http://stackoverflow.com/questions/36589452/in-firebase-how-can-i-query-the-most-recent-10-child-nodes/36665442#36665442) – Jay Apr 04 '17 at 17:34
  • @Jay, doesn't your answer assume that the timestamp is generated on the client, and not with firebase.database.ServerValue.TIMESTAMP, which is an Object and cannot be set to a negative number on the client? – Vitali Kniazeu Apr 11 '17 at 20:52
  • @VitaliKniazeu Yes! That's exactly what it does and that's by design. Your comment is really good so I crafted another solution (see below) to address your use case as well. – Jay Apr 12 '17 at 21:25
  • @Jay I actually don't work with Swift, but was looking to a solution for my web / Ionic / Firebase setup. I ended up with this solution https://groups.google.com/forum/#!topic/firebase-talk/EXMbZmyGWgE, which let's me grab the offset once and generate a pretty accurate negative timestamp server-based value on the client. Thank you for your suggestions though! – Vitali Kniazeu Apr 13 '17 at 14:11

2 Answers2

4

First, see the linked answer in the comments. That answer relies on the client to generate a timestamp that's made negative and written to Firebase.

If you want to have Firebase generate a timestamp, that can be done as well with this little snappy Firebase structure and piece of code.

First, let's take a look at the structure

root
  parent_node
    -Y8j8a8jsjd0adas
       time_stamp: -1492030228007
  timestamp: 1492030228007

Next, some code to create and work with that structure:

Define a var we can use within our class that references the Firebase time stamp

let kFirebaseServerValueTimestamp = [".sv":"timestamp"]

and a function that adds an observer to the timestamp node:

func attachObserver() {

    let timestampRef = self.ref.child("timestamp")
    let parentNodeRef = self.ref.child("parent_node")
    var count = 0

    timestampRef.observe(.value, with: { snapshot in

        if snapshot.exists() {
            count += 1
            if count > 1 {
                let ts = snapshot.value as! Int
                let neg_ts = -ts
                let childNodeRef = parentNodeRef.childByAutoId()
                let childRef = childNodeRef.child("time_stamp")
                childRef.setValue(neg_ts)
                count = 0
            }
        }
    })

And a function that writes out a timestamp, therefore causing the observer to fire which creates child nodes within the parent_node based on the Firebase time stamp

func doTimestamp() {
    let timestampRef = self.ref.child("timestamp")
    timestampRef.setValue(kFirebaseServerValueTimestamp)
}

Here's the rundown.

In the attachObserver function, we attach an observer to the timestamp node - that node may or may not exist but if it doesn't it will be created - read on. The code in the closure is called any time an event occurs in the timestamp node.

When the doTimestamp function is called, it creates and writes a timestamp to the timestamp node, which then fires the observer we attached in attachObserver.

The code in the observe closure does the following:

Make sure the snapshot contains something, and if it does, increment a counter (more on that in a bit). If the counter is greater than 1 get the timestamp as an integer from the snapshot. Then, create it's negative and write it back out to Firebase as a child of parent_node.

How this would apply would be anytime you want to timestamp a child node with a Firebase generated timestamp but negative value for reverse loading/sorting - which speaks to the OP question.

The gotcha here is that when this happens

    timestampRef.setValue(kFirebaseServerValueTimestamp)

It actually writes twice to the node, which would cause the code in the closer to be called twice.

Maybe a Firebaser can explain that, but we need to ignore the first event and capture the second, which is the actual timestamp.

So the first event will cause the observer closer to fire, making count = 1, which will be ignored due to the if statement.

Then the second event fires, which contains the actual timestamp, and that's what we use to make negative and write out to Firebase.

Hope this helps the OP and the commenters.

Jay
  • 34,438
  • 18
  • 52
  • 81
2

Regardless whether it's for Swift or not, another conceptual solution is to rely on Firebase's server time offset value.

It's not as precise as firebase.database.ServerValue.TIMESTAMP, but the difference is usually within milliseconds. The advantage is that it lets you create a negative timestamp on the client without having to update your Firebase node twice.

You grab the server time offset value when you need it from Firebase, generate the negative timestamp on the client, and then save your object in Firebase once.

See: https://groups.google.com/forum/#!topic/firebase-talk/EXMbZmyGWgE https://firebase.google.com/docs/database/ios/offline-capabilities#clock-skew (for iOS). https://firebase.google.com/docs/database/web/offline-capabilities#clock-skew (for web).

var offsetRef = firebase.database().ref(".info/serverTimeOffset");
offsetRef.on("value", function(snap) {
  var offset = snap.val();
  var negativeTimestamp = (new Date().getTime() + offset) * -1;
});
Vitali Kniazeu
  • 1,077
  • 9
  • 10