Search code examples
swiftfirebasefirebase-realtime-databaseobservers

.childAdded observer doesn't get called If the function is not called explicitly. Swift 4


I have a function that should listen to a Firebase node and get a snapshot of new posts when they get posted, but the function is not getting galled at all, as if the observer .observe(DataEventType.childAdded, with: { (snapshot) in didn't see new posts in the node. I checked and new posts are indeed registered in real time in Firebase. Should I call the function or is the observer that should do it? Heres the complete function:

func getNewerAlerts(setCompletion: @escaping (Bool) -> ()) {

        print("                     MapArray.alertNotificationCoordinatesArray before  getNewerAlerts snapshot is: \(MapArray.alertNotificationCoordinatesArray)")
        print("                     self.userAlertNotificationArray before getNewerAlerts snapshot is: \(self.userAlertNotificationArray)")

        ref = Database.database().reference()

        ref?.child("Continent").child("Europe").child("Country").child("Italy").child("Region").child("Emilia-Romagna").child("City").child("Bologna").child("Community").child("Alert Notifications").observe(DataEventType.childAdded, with: { (snapshot) in
            print("         snapshot is: \(snapshot)")
            guard let data = snapshot.value as? [String:[String:String]] else { return }
            guard let firebaseKey = snapshot.key as? String else { return }
            //                let date = data!["Date"]
            //                let time = data!["Time"]
            data.values.forEach {
                let dataLatitude = $0["Latitude"]!
                let dataLongitude = $0["Longitude"]!

                let type = $0["Description"]!
                let id = Int($0["Id"]!)
                let doubledLatitude = Double(dataLatitude)
                let doubledLongitude = Double(dataLongitude)
                let recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)

                //            print("Firebase alerts posts retrieved")

                let userAlertAnnotation = UserAlert(type: type, coordinate: recombinedCoordinate, firebaseKey: firebaseKey, title: type,id: id!)

                self.mapView.addAnnotation(userAlertAnnotation)
                self.userAlertNotificationArray.append(userAlertAnnotation)
                MapArray.alertNotificationCoordinatesArray.append(recombinedCoordinate)
            }
            print("                 MapArray.alertNotificationCoordinatesArray after getNewerAlerts snapshot is: \(MapArray.alertNotificationCoordinatesArray)")
            print("                     self.userAlertNotificationArray after getNewerAlerts snapshot is: \(self.userAlertNotificationArray)")
            setCompletion(true)
        })

    }

Thank you very much.

EDIT Rewritten function :

func getAlerts(setCompletion: @escaping (Bool) -> ()) {

        self.mapView.removeAnnotations(mapView.annotations)
        MapArray.alertNotificationCoordinatesArray.removeAll()
        MapArray.userAlertNotificationArray.removeAll()

        print("                     MapArray.alertNotificationCoordinatesArray before getNewerAlerts is: \(MapArray.alertNotificationCoordinatesArray)")
        print("                     self.userAlertNotificationArray before getNewerAlerts is: \(MapArray.userAlertNotificationArray)")

        ref = Database.database().reference()
//        ref?.child("Continent").child("Europe").child("Country").child("Italy").child("Region").child("Emilia-Romagna").child("City").child("Bologna").child("Community").child("Alert Notifications").observe(.childAdded, with: { (snapshot) in
        ref?.child("Continent").child("Europe").child("Country").child("Italy").child("Region").child("Emilia-Romagna").child("City").child("Bologna").child("Community").child("Alert Notifications").observe(DataEventType.childAdded, with: { (snapshot) in
            //            self.mapView.removeAnnotations(self.mapView.annotations) //
            print("        added snapshot is: \(snapshot)")
            guard let data = snapshot.value as? [String:String] else { return }
//            guard let firebaseKey = snapshot.key as? String else { return }
            let firebaseKey = snapshot.key
            //                let date = data!["Date"]
            //                let time = data!["Time"]
            let dataLatitude = data["Latitude"]!
            let dataLongitude = data["Longitude"]!

            let type = data["Description"]!
            let id = Int(data["Id"]!)
            let userName = data["user"]!
            let doubledLatitude = Double(dataLatitude)
            let doubledLongitude = Double(dataLongitude)
            let recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)

            let userAlertAnnotation = UserAlert(type: type, coordinate: recombinedCoordinate, firebaseKey: firebaseKey, title: type,id: id!, userName: userName)
            MapArray.userAlertNotificationArray.append(userAlertAnnotation)  // array of notifications coming from Firebase
            MapArray.alertNotificationCoordinatesArray.append(recombinedCoordinate) // array for checkig alerts on route
                        print("                 MapArray.alertNotificationCoordinatesArray after getNewerAlerts is: \(MapArray.alertNotificationCoordinatesArray)")
                        print("                     self.userAlertNotificationArray after getNewerAlerts is: \(MapArray.userAlertNotificationArray)")
            setCompletion(true)
            self.mapView.addAnnotations(MapArray.userAlertNotificationArray)
        })

//        ref?.child("Continent").child("Europe").child("Country").child("Italy").child("Region").child("Emilia-Romagna").child("City").child("Bologna").child("Community").child("Alert Notifications").observe(.childRemoved, with: { (snapshot) in
        ref?.child("Continent").child("Europe").child("Country").child("Italy").child("Region").child("Emilia-Romagna").child("City").child("Bologna").child("Community").child("Alert Notifications").observe(DataEventType.childRemoved, with: { (snapshot) in

            print("    self.userAlertNotificationArray before getDeletedAlerts snapshot is: \(MapArray.userAlertNotificationArray)")
            print("    MapArray.alertNotificationCoordinatesArray before getDeletedAlerts snapshot is: \(MapArray.alertNotificationCoordinatesArray)")

            print("        removed snapshot is: \(snapshot)")
            guard let data = snapshot.value as? [String:String] else { return }
            let firebaseKey = snapshot.key
            //                let date = data!["Date"]
            //                let time = data!["Time"]
            let dataLatitude = data["Latitude"]!
            let dataLongitude = data["Longitude"]!

            let type = data["Description"]!
            let id = Int(data["Id"]!)
            let userName = data["user"]!
            let doubledLatitude = Double(dataLatitude)
            let doubledLongitude = Double(dataLongitude)
            let recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)


            _ = UserAlert(type: type, coordinate: recombinedCoordinate, firebaseKey: firebaseKey, title: type,id: id!, userName: userName)

            MapArray.userAlertNotificationArray.removeAll(where: { ($0.firebaseKey == firebaseKey) }) //remove the alert
            MapArray.alertNotificationCoordinatesArray.removeAll(where: { ($0.latitude == recombinedCoordinate.latitude && $0.longitude == recombinedCoordinate.longitude) })

            self.mapView.removeAnnotations(self.mapView.annotations)
            self.mapView.addAnnotations(MapArray.userAlertNotificationArray)

                        print("    self.userAlertNotificationArray after getDeletedAlerts snapshot is: \(MapArray.userAlertNotificationArray)")
                        print("    MapArray.alertNotificationCoordinatesArray after getDeletedAlerts snapshot is: \(MapArray.alertNotificationCoordinatesArray)")
            setCompletion(true)
        })


    }

Solution

  • Instead of a lengthy discussion in comments let's try to answer this with a couple of links and a code example.

    First though, you should only synchronize data when a view is visible and the viewWillAppear method is called each time the view becomes visible, so that's a good place to add observers. It's also good practice to remove observers when you don't need them (saves bandwidth) and that can be done using a firebase handle in the viewDidDisappear. Here's a slightly dated article but a great read

    Best Practices for UIViewController and Firebase

    and for a great example, see the answer to this question

    Firebase: when to call removeObserverWithHandle in swift

    To address the rest of the question (note I am keeping this short so I didn't include using handles)

    I have a class to store alerts

    class AlertClass {
        var node_key = ""
        var msg = ""
    
        init(aKey: String, aMsg: String) {
            self.node_key = aKey
            self.msg = aMsg
        }
    }
    

    and then a class var array to store all of the alerts

    var alertArray = [AlertClass]()
    

    and then we add our observers from the viewWillAppear function

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.addObservers()
    }
    

    which adds three observers to the referenced node; .childAdded, .childChanged, and .childRemoved. Keep in mind that .childAdded will iterate over the nodes in the ref node and populate our dataSource any time viewWillAppear is called, so we need to 'reset' the array so we don't accidentally load data on top of the existing data. Your use case may differ so code accordingly.

    Here's the code to add the observers and prints the array any time there's a change.

    func addObservers() {
        let ref = self.ref.child("Continent").child("Europe").child("Country").child("Italy").child("Region").child("Emilia-Romagna").child("City").child("Bologna").child("Community").child("Alert Notifications")
        self.alertArray = []
        ref.observe(.childAdded, with: { (snapshot) in
            let key = snapshot.key
            let msg = snapshot.childSnapshot(forPath: "msg").value as! String
            let aAlert = AlertClass(aKey: key, aMsg: msg)
            self.alertArray.append(aAlert) //append the new alert
            self.showAlertArray() //this is called for every child
        })
    
        ref.observe(.childChanged, with: { (snapshot) in
            let key = snapshot.key
            let msg = snapshot.childSnapshot(forPath: "msg").value as! String
            if let foundAlert = self.alertArray.first(where: { $0.node_key == key } ) {
                foundAlert.msg = msg //update the alert msg
                self.showAlertArray()
            }
        })
    
        ref.observe(.childRemoved, with: { (snapshot) in
            let key = snapshot.key
            self.alertArray.removeAll(where: { $0.node_key == key }) //remove the alert
            self.showAlertArray()
        })
    }
    
    func showAlertArray() {
        for alert in self.alertArray {
            print(alert.node_key, alert.msg)
        }
    }
    

    and as a side note...

    If you're populating a tableView dataSource via the childAdded you may be wondering how to do that without calling tableView.reloadData repeatedly, which may cause flicker. There's a techniqure for doing that by leveraging the fact that .value events are called after .childAdded. See my answer to this question for an example.