-1

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)
        })


    }
Vincenzo
  • 5,304
  • 5
  • 38
  • 96
  • 1
    Your observer code works for me (I removed the rest to test). The first question is that, assuming there is an alert in the Alert Notifications child node, does it print that snapshot when first run but doesn't after that? Or does nothing print when you first run the code? If nothing prints then either this function is not called or your observer is not pointing to the correct node. You may also want to consider denormalizing your structure as that's pretty deep. – Jay Mar 02 '19 at 14:20
  • @Jay Yes in the node there is child in then node, but when I add a new child from another device the function doesn't get called at all. My doubt is: Shouldn't the function get called automatically? Or I should call it and then it will keep getting new Childs? If I have to call the function, wouldn't it get all the Childs at first and then gets the new Childs afterward? If it is so I would get the entire node every time the app starts and that would result in a pretty big monthly download right? – Vincenzo Mar 02 '19 at 18:04
  • I ask because I have another function that gets the entire node when the app first start using `.observeSingleEvent(of: .value, with: { (snapshot)` in `viewWillAppear()` and from than `getNewerAlerts`will get new Childs. Is my logic wrong? – Vincenzo Mar 02 '19 at 18:07
  • The code within the closure will get called whenever a child is addd to that node from any device. It does work reliably so I would guess that maybe your view is going out of scope or again, the node it not being pointed to correctly. Getting the entire node may be fine - however it depends on how much data is within the node. Any time you add a .childAdded event to a node, it's going to iterate over all child nodes so doing it twice probable isnt necessary. – Jay Mar 02 '19 at 20:49
  • @Jay. I think I understood what I haven't before. Snapshot from `.observeSingleEvent(of: .value, with: { (snapshot)`observer would be the entire node so it's `[String:[String:String]]`, and snapshot from `.childAdded, with: { (snapshot) in`only contains a node's child so it will be `[String:String]' I can either use one or the other only called in `viewWillAppear`right? – Vincenzo Mar 02 '19 at 22:23
  • Where do I use `.observe(.childRemoved, with: { (snapshot) in`than?Do I have to also call the function using that observer in `viewWillAppear`or can I put two observers within the same function? – Vincenzo Mar 02 '19 at 22:28
  • Typically, if you are interested in added, changed or removed you would add three observers on a node. See [Listen for child events](https://firebase.google.com/docs/database/ios/lists-of-data#listen_for_child_events). Those will deliver a single event within the closure for each. If you get the entire node, which would be .value, you're correct in that each child within the node will be iterated over to get its children whereas a .childAdded will deliver each node separately. – Jay Mar 02 '19 at 22:49
  • @Jay yes, I was just trying that out and realised it. So I could either have one function with two or more observers or a separate function per observer right?I think I finally understood observers. Thank you very much – Vincenzo Mar 02 '19 at 23:17
  • @Jay. One last thing is buggin me now... if i call the `.childAdded` function in `viewWill` appear than every time a user leaves `mapVc` and re enters it will download the entire node again right? How to only download newer data? Because my `mapVc` will be opened many times a day ..per user... – Vincenzo Mar 03 '19 at 07:42
  • I did a little test. If I don't remove the observer in `mapVc`'s `viewWillDisappear()`: At 1st `viewDidLoad`: arrays prints are empty arrays. FINE. At 1st `viewDidAppear`: `getNewerAlerts`(1st: arrays prints are empty arrays. 2nd: arrays get populated from snapshot as expected. 3rd: arrays prints are populated arrays.) FINE. At 2nd `viewDidLoad`: arrays prints are populated arrays. FINE. At 2nd `viewDidAppear`: `getNewerAlerts`(1st: arrays prints are empty arrays. (NOT FINE ) 2nd: arrays get populated from snapshot as expected. 3rd: arrays prints are populated arrays.) NOT FINE. – Vincenzo Mar 03 '19 at 08:57
  • So my concern is why at 2nd `viewDidAppear`, `getNewerAlerts`calling beginning doesn't print populated array as `viewDidLoad`does? I also tried to reload`mapVc`without connection and it print's a snapshot anyways, so that would mean that read cached snapshot and it doesn't download from Firebase Avery time but just once at app launch and then just newer posts right? If so it's a good news. – Vincenzo Mar 03 '19 at 08:57

1 Answers1

1

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.

Jay
  • 34,438
  • 18
  • 52
  • 81
  • Thank you so much for this exhausting explanation, links and code examples. It really made me understand things and I can't express enough how grateful I am for your help. I was struggling with this and through quite a lot of try and error I did solve some of this, but the Best Practice and the other links are gold. Thank you s much again. Vinny – Vincenzo Mar 03 '19 at 19:30
  • I get back after a few days of tries. I found that using `.observeSingleEvent`only reads existing `children.` but doesn't get new ones as they get added from different users.. Using `.observe(.childAdded` instead as you wrote in your answer code gives me multiple reads of the newer added child like in a loop and generates multiples of the same annotations on map even dough it deletes them all.. But using `.observe(DataEventType.childAdded`gives me existing children and a child only when it gets added or deleted. It now works as I intended it to be. – Vincenzo Mar 24 '19 at 09:52
  • I can't understand the difference between the code you suggested in your answer and mine. I put the rewritten function in question edits. Thank you so much – Vincenzo Mar 24 '19 at 09:53
  • @vincenzo To the first comment; *.observe(.childAdded* and *.observe(DataEventType.childAdded* are identical if that was the question. I don't see anything in that code that would delete them all. To the second comment: the code looks ok to me. Is it not working? Also, `self.mapView.addAnnotations(MapArray.userAlertNotificationArray)` in the .childRemoved closure seems a little odd since you would be removing, not adding. Also, the `alertNotificationCoordinatesArray` stores *CLLocationCoordinate2D* objects, but when you remove one, you're doing it by the actual coordinates. Does that work? – Jay Mar 24 '19 at 13:21
  • starting from the bottom. I remove snapshot object from `userAlertNotificationArray`,which is for displaying annotations on the map, then I remove all annotations and add the entire updated array to the map. that works as expected. I also remove snapshot object from `alertNotificationCoordinatesArray` that's a mirror array that I use for other purposes, and removal by actual coordinates works. – Vincenzo Mar 24 '19 at 14:59
  • for the observer part if I use `.observe(.childAdded`instead of `.observe(DataEventType.childAdded` I get a very weird behaviour. for every snapshot I get in , eighter added o removed, I get an exponential number of prints in console. Starting fresh with zero entries in Firebase, at my first annotation add, I get the `childAdded` twice, and when I remove that annotation I get the `.childRemoved' snapshot prints 3 times..1st showing array with 1 entry before, and array with 0 entries after, 2nd and 3rd showing empty arrays before and after.. – Vincenzo Mar 24 '19 at 15:06
  • "I don't see anything in that code that would delete them all." I meant that `.childRemoved`removes all the extra copies, even though not always, some times I had to delete 8 copies of the same on annotation I added.. – Vincenzo Mar 24 '19 at 15:15
  • Very odd. Can you verify your Xcode and Firebase are up-to-date? There should be no functional difference between the two .childAdded you mentioned. Also, .childRemoved is only fired for a single child. The updated code only removes the object from the array with `removeAll(where: { ($0.firebaseKey..`. I understand you have 'duplicates' but that tells me there something else in the code outside of those two firebase calls that's causing the issue. .childAdded fires once for each child node - I've never seen it fail. Perhaps something else is writing data to the node causing the event? – Jay Mar 24 '19 at 15:43
  • I also though that some other thing was causing that call, so I put breakpoints on the prints lines and I notices that the observers loop. only with the`observe(.childAdded`and not with the `.observe(DataEventType.childAdded`.. – Vincenzo Mar 24 '19 at 15:55
  • .. I actually updated pods after solving it with `.observe(DataEventType.childAdded` il'' test `observe(.childAdded` with updated pos and tell you – Vincenzo Mar 24 '19 at 16:01
  • this is getting scary.. using `.observe(DataEventType.childAdded` was working as expected.. 1 print per snapshot.. i went back to `observe(.childAdded`to try if it was the outdated pods but no, same result. I reactivated the `.observe(DataEventType.childAdded` and it's now behaving as the other.. What??! – Vincenzo Mar 24 '19 at 16:18
  • @vincenzo You've definitely got something odd going on. You may want to clean and rebuild to start with. Also, if you look at the [Firebase Documentation](https://firebase.google.com/docs/database/ios/lists-of-data#listen_for_child_events) it clearly shows `commentsRef.observe(.childAdded, with: { (snapshot)` so that should be working as advertised. You may still have outdated files or podFile. I still think you've got code elsewhere in your project causing .childAdded events to fire. – Jay Mar 24 '19 at 16:29
  • I notices something that could help understand what's going on. If I add and remove annotation from app running on my iPhone while running app in simulator, I get normal number of prints. 1 when add and 1 when delete annotation. If I then add and delete from simulator, the weird behaviour starts. 2 prints per add/deletion even if done from iPhone.. – Vincenzo Mar 24 '19 at 16:42