Search code examples
iosswiftfirebasefirebase-realtime-databasensnull

Swift does not retrieve Firebase values (returns values as NSNull)


I'm trying to pull values from my Firebase real-time database, but for whatever reason the values come back as NSNull. I was getting error 'Could not cast value of type 'NSNull' to 'NSNumber' on line 13 until I changed 'let count = ...' to an 'if let'. I'm not sure why these values are returning null when they are not.

I've already tried printing the retrieved snapshot, and, as I expected, none of the values are null. I am stumped. Here is the log I get when printing the retrieved snapshot:

[Snap (A12-1A) 0, Snap (B1-2A) 0, Snap (C2-3A) 0, Snap (D3-4A) 0, Snap (E4-5A) 0, Snap (F5-6A) 0, Snap (G6-7A) 0, Snap (H7-8A) 0, Snap (I8-9A) 0, Snap (J9-10A) 5, Snap (K10-11A) 0, Snap (L11-12P) 0, Snap (M12-1P) 0, Snap (N1-2P) 0, Snap (O2-3P) 0, Snap (P3-4P) 0, Snap (Q4-5P) 0, Snap (R5-6P) 0, Snap (S6-7P) 0, Snap (T7-8P) 0, Snap (U8-9P) 0, Snap (V9-10P) 0, Snap (W10-11P) 0, Snap (X11-12A) 0]

All of the data in this snapshot is the data in my Firebase database, so I'm not sure why I'm getting this error.

My firebase setup: calendar-signups --- 04132019 --- (A12-1A: 0, B1-2A: 0, ..., X11-12P: 0)

func getCalendarSignupsOnDate(selectedDate: String, handler: @escaping (_ dateSignups: [Calendar]) -> ()) {
        var calendarArray = [Calendar]()

        REF_CALENDAR_SIGNUPS.observeSingleEvent(of: .value) { (snapshot) in
            guard let snapshot = snapshot.children.allObjects as? [DataSnapshot] else { return }

            var doesContainDate = false
            for date in snapshot {
                if date.key == selectedDate {
                    doesContainDate = true

                    self.REF_CALENDAR_SIGNUPS.child(date.key).observeSingleEvent(of: .value, with: { (dateSnapshot) in
                        guard let dateSnapshot = dateSnapshot.children.allObjects as? [DataSnapshot] else { return }

                        for timeSlots in dateSnapshot {
                            let time = timeSlots.key
                            if let count = timeSlots.childSnapshot(forPath: "J9-10A").value as? Int {
                                let calendarData = Calendar(time: time, signupCount: count)
                                calendarArray.append(calendarData)
                                handler(calendarArray)
                            } else {
                                let count = 0
                                let calendarData = Calendar(time: time, signupCount: count)
                                calendarArray.append(calendarData)
                                handler(calendarArray)
                            }
                        }
                    })
                }
            }
            for item in calendarArray {
                print(item.time + " " + String(item.signupCount))
            }
            if doesContainDate == false {
                let dateSignUpData = [
                    "A12-1A": 0,
                    "B1-2A": 0,
                    "C2-3A": 0,
                    "D3-4A": 0,
                    "E4-5A": 0,
                    "F5-6A": 0,
                    "G6-7A": 0,
                    "H7-8A": 0,
                    "I8-9A": 0,
                    "J9-10A": 0,
                    "K10-11A": 0,
                    "L11-12P": 0,
                    "M12-1P": 0,
                    "N1-2P": 0,
                    "O2-3P": 0,
                    "P3-4P": 0,
                    "Q4-5P": 0,
                    "R5-6P": 0,
                    "S6-7P": 0,
                    "T7-8P": 0,
                    "U8-9P": 0,
                    "V9-10P": 0,
                    "W10-11P": 0,
                    "X11-12A": 0
                    ]
                    as [String : Int]

                self.REF_CALENDAR_SIGNUPS.child(selectedDate).updateChildValues(dateSignUpData)

                for timeSlots in dateSignUpData {
                    let time = timeSlots.key
                    let count = timeSlots.value
                    let calendarData = Calendar(time: time, signupCount: count)
                    calendarArray.append(calendarData)
                }
            }

            handler(calendarArray)
        }
    }

Solution

  • This isn't really going to be a complete answer but maybe provide you the right direction. There are some confusing bits to the code so I'll review those and then possibly provide a better direction...

    Your function is passed a single selected date

    func getCalendarSignupsOnDate(selectedDate: String
    

    which means you know the date node you're trying to read. However, the code is reading in ALL the dates and iterating over them trying to find that date

     for date in snapshot {
       if date.key == selectedDate {
    

    It would probably be a lot less code to just directly read that node, then work with the child nodes within it.

    Also, further down in the code, you're iterating over the child nodes of A12-1A, B1-2A, looking for specific node

    if let count = timeSlots.childSnapshot(forPath: "J9-10A").value as? Int
    

    which won't work at all because timeSlots A12-1A etc have values, all 0, not child snapshots. The code continues and appears that you want to read the value of that specific child node and then set the rest to 0 which are added to your calendar array.

    let count = 0
    let calendarData = Calendar(time: time, signupCount: count)
    

    But then farther down, if you don't find that timeslot, all of the child nodes are set to 0 anyway.

    if doesContainDate == false {
        let dateSignUpData = [
           "A12-1A": 0,
           "B1-2A": 0,
    

    So... I think I get the gist of what you're trying to do. Here's some code that may get you going the right direction. This should replace all of the code in your question.

    func getCalendarSignupsOnDate(selectedDate: String) {
        var dateSignUpData = [
            "A12-1A": 0,
            "B1-2A": 0,
            "C2-3A": 0,
            "J9-10A": 0
        ]
        let slotYourLookingFor = "J9-10A"
        let dateRef = self.REF_CALENDAR_SIGNUPS.child(selectedDate).child(slotYourLookingFor)
        dateRef.observeSingleEvent(of: .value, with: { snapshot in
            if snapshot.exists() {
                let count = snapshot.value as? Int ?? 0
                dateSignUpData[slotYourLookingFor] = count //update this array element with the new count from Firebaes
            } else { //didn't find the child node at all, will create the date node and add child nodes with val = 0
                self.REF_CALENDAR_SIGNUPS.child(selectedDate).updateChildValues(dateSignUpData)
            }
            //do something with the dateSignUpData array
        })
    }
    

    Explanation:

    Pass in the date you are interested in. The code will read that node with child node of "J9-10A", the one you are looking for.

    If it exists, it will read that data and update the dateSignUpData array with the count from that child node. You can then process the updated array further.

    If the "J9-10A" node does not exist, a node with the date passed in will be created, and then all child node slots will be set to 0. You can then process the array further.

    Last thing. Probably a good idea to store dates as yyyymmdd for ordering.