Search code examples
swiftfirebasetimestampgoogle-cloud-firestorecountdown

How to prevent button text refresh lag when retrieving data from Firestore?


I'm using Google Firestore as my backend server for an iOS app I'm making. I am trying to incorporate a countdown timer to represent when the game starts, however, it is vital to use network time in oppose to device time to ensure the game starts for everyone at the same time.

The basic idea is to retrieve the current timestamp from Firebase, subtract it from another timestamp value of when I want the game to start and then keep refreshing the button text every second (not label because I need to be able to press it to reveal further information) with the time difference.

When the app starts, the timer works really well with no/very minimal lag however, if I change the value of when I want the game to start, the button text glitches for some reason. Even in the debug area, I can see that it's lagging.

I've done all the math and conversions. The current timestamp is a timeIntervalSince1970 value which I converted to a string with format "HH:mm:ss". I am retrieving this value from firebase server value. The other timestamp in which when I want the game to start is a string value I have stored in a collection in Firestore.

I send these two string values to a function that finds the difference for me and I set the button text to the value. It's simple but I cannot understand why it starts to lag when I change the second timestamp value. If I change it a third time, the app still runs but the timer basically freezes every 8 seconds or so.

override func viewDidAppear(_ animated: Bool) {

    DispatchQueue.main.async {

    let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in

            Firestore.firestore().collection("countdown").document("thursday")
                .addSnapshotListener { documentSnapshot, error in
                    guard let document = documentSnapshot else {
                        print("Error fetching document: \(error!)")
                        return
                    }
                    guard let theTime = document.data() else {
                        print("Document data was empty.")

                        return
                    }
                    print("Current data: \(theTime)")

            let stringData = "\(theTime)"
            let finalTime = stringData.substring(from: 9, to: 16) 

// Maybe not the most conventional method to get the right format but it works for me. I'm open to suggestions of course tho.

            var currentTimeStamp: Double?

            let ref = Database.database().reference().child("serverTimestamp")

            ref.setValue(ServerValue.timestamp())

            ref.observe(.value, with: { snap in
                if let t = snap.value as? Double {

                    currentTimeStamp = t/1000

                    let unixTimestamp = currentTimeStamp
                    let date = Date(timeIntervalSince1970: unixTimestamp!)
                    let dateFormatter = DateFormatter()
                    dateFormatter.timeZone = TimeZone(abbreviation: "EDT") //Set timezone that you want
                    dateFormatter.locale = NSLocale.current
                    dateFormatter.dateFormat = "HH:mm:ss" //Specify your format that you
                    let strDate = dateFormatter.string(from: date)

                    let dateDiff = self.findDateDiff(time1Str: strDate, time2Str: finalTime)
                    print("This is the countdown time: \(dateDiff)")

                }
            })
        }
    }
    }

// This is just a peak inside the function that does all the math and outputs the countdown time in the button text. I have many if statements for all the scenarios for the time to math the "HH:mm:ss" format.

if hours >= 10 && minutes >= 10 && seconds < 10{

        self.time.setTitle(("\(Int(hours)):\(Int(minutes)):0\(Int(seconds))"), for: .normal)

I expect the button text to refresh with the correct countdown time. I expect that when I change my Firestore timestamp value, the button text refreshes automatically and does not lag.

The actual result is that it lags.


Solution

  • So I'm not sure if I have this correct, but my understanding is that you're setting a Timer object to fire off an .observe call each second? If so your Firebase calls aren't retrieving the data in time (.observe is async, so that's where the lag comes from).

    What I would suggest doing instead would be to first set the timestamp you're counting down to and post that value to Firebase.

    When each device needs the countdown button, they retrieve the timestamp, then set the Timer object to fire in 1 second intervals to retrieve the current time from something like NSDate().timeIntervalFrom1970, find the difference between the two, then set the button text (and deactivate the Timer once the countdown is over). Maybe something like this?:

    override func viewDidAppear(_ animated: Bool) {
    
        // get time to count down from
        Firestore.firestore().collection("countdown").document("thursday").getDocument { (document, error) in
    
            guard let document = document, document.exists else {
                // document doesn't exist
                return
            }
    
            guard let theTime = document.data() else {
                print("Document data was empty.")
                return
            }
    
            print("Current data: \(theTime)")
    
            let stringData = "\(theTime)"
            let finalTime = stringData.substring(from: 9, to: 16)
            self.setTimer(for: finalTime)
        }
    }
    
    // set Timer
    
    func setTimer(for finalTime: String) {
    
        let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
    
            let timeNow = Date()
    
            let dateFormatter = DateFormatter()
            dateFormatter.timeZone = TimeZone(abbreviation: "EDT")
            dateFormatter.locale = NSLocale.current
            dateFormatter.dateFormat = "HH:mm:ss"
    
            let strDate = dateFormatter.string(from: timeNow)
    
            // stop timer once finalTime reached. Idk what finalDate is but here's if it's formatted the same as strDate is.
            guard finalTime != strDate else {
                timer.invalidate()
                print("countdown over")
    
                return
            }
    
            let dateDiff = self.findDateDiff(time1Str: strDate, time2Str: finalTime)
    
            print("This is the countdown time: \(dateDiff)")
        }
    
        timer.fire()
    }
    

    Also, I would suggest comparing timestamp values as Doubles and then formatting the difference how you need to instead of formatting each variable and then comparing them as strings