0

I am having a bit of trouble with swift and firebase. Here is my problem: I want to get a string from my firebase database. So I get it convert it into an int on line 5. Then in the next function I want to for however many times one of days is false I want to take the didntPractiseInt variable and add 1 to it. The problem is it doesn't seem to be adding in the print statement (on the next line).

var didntPractiseInt = 0

self.practise?.ref?.observeSingleEvent(of: .value, with: { (snapshot) in
    let snapshot = snapshot.value as? NSDictionary
    let practised = snapshot?["didntPractiseCounter"] as? String ?? ""
    self.didntPractiseInt = Int(practised)!
})

day.ref?.observeSingleEvent(of: .value, with: { (snapshot) in
    let snapshot = snapshot.value as? NSDictionary
    let practised = snapshot?["practised"] as? String ?? ""
    if practised == "false" {
        self.didntPractiseInt += 1
        print(self.didntPractiseInt)
    }
})

// PRINTS 22222

The funny thing about the print statement is that it prints the right amount of false(s) but doesn't add them every time.

Thanks to anyone who contributes!

My Firebase database:

enter image description here

André Kool
  • 4,880
  • 12
  • 34
  • 44
  • Why not just save it as an int in your database? – André Kool Apr 04 '18 at 10:04
  • don't know just easier for me to code. Does it matter? –  Apr 04 '18 at 10:05
  • 1
    If its an int you should treat it as an int. That way you wont have to cast it from string to int every time you want to do something like adding or substracting. – André Kool Apr 04 '18 at 10:09
  • Does it matter though? –  Apr 04 '18 at 10:09
  • And anyway I still need to set that variable –  Apr 04 '18 at 10:15
  • I dont know anything about swift/ios so i dont really know if it matters. But looking at your code you never save `didntPractiseCounter` in your database. And how many times is this line being called: `self.didntPractiseInt = Int(practised)!`? – André Kool Apr 04 '18 at 11:29
  • One issue here is if the code is run as-is, sometimes the second observe closure will run before the first observe closure has completed and it will *print(self.didntPractiseInt)* before *self.didntPractiseInt = Int(practised)!*. Other times it may not. This is called asynchronous behavior and there are a lot of posts about it here on SO. It is by design and it's because code in your machine runs a lot faster than the internet can respond to requests. See [this](https://stackoverflow.com/questions/43823808/access-firebase-variable-outside-closure/43832208#43832208). – Jay Apr 06 '18 at 18:00

1 Answers1

0

The second call needs to come after the first call per my comment. Firebase is already asynchronous and to ensure things happen in sequence they need to be treated as such within the Firebase closure. So a typical pattern is

addObserver {
   receive a valid snapshot {
      do something with snapshot data as it's only valid at this point
   }
}

Also, we can do this entirely in Swift and since you know what the node names are, they can be accessed directly instead of working through a NSDictionary. I've also include basic error checking to catch nil nodes.

var didntPractiseInt = 0

func checkDidntPracticeCount() {
    let whichNodeRef = self.ref.child("practice").child("some_node")
    let didntPracticeCounterRef = whichNodeRef.child("didntPracticeCounter")

    didntPracticeCounterRef.observeSingleEvent(of: .value, with: { snapshot in
        if let didntPracticeCount = Int(snapshot.value as! String) {
            self.didntPractiseInt = didntPracticeCount
            self.checkDidPracticeOnADay()
        } else {
            print("didntPracticeCounter node not found")
        }
    })
}

func checkDidPracticeOnADay() {
    let whichNodeRef = self.ref.child("practice").child("some_node")
    let dayNum = "0"
    let whichDayRef = whichNodeRef.child("days").child(dayNum).child("practiced")
    whichDayRef.observeSingleEvent(of: .value, with: { snapshot in
        if let didPractice = snapshot.value as? String {
            if didPractice == "false" {
                self.didntPractiseInt += 1
                print(self.didntPractiseInt)
            } else {
                print("did practice on day \(dayNum)")
            }
        }
    })
}

Note that self.ref points to my Firebase as a class var.

I would also suggest using boolean values instead of "false" and "true" and you may want to consider storing your counters as Int's instead of String's as they may be a bit easier to work with for sorting etc.

Lastly, array's can be bad news in NoSQL databases and their use is very situational. You may want to consider storing your days some other way. It may be ok in this case but please take a look at Arrays Are Evil. It's 2014 post but still very applicable.

Edit: I was thinking about this and given there will always be 7 days in your days node, there may be a simpler approach. I no idea if this well help but a simple query will provide a count of how many days a user practiced during a given week.

let daysRef = self.ref.child("practice").child("some_node").child("days")
let query = daysRef.queryOrdered(byChild: "practiced").queryEqual(toValue: "true")
query.observeSingleEvent(of: .value, with: { snapshot in
    print(snapshot.childrenCount)
})

If that's the case then all of the code in the first part in my answer could be replaced with this code.

Jay
  • 34,438
  • 18
  • 52
  • 81