Mr10k Mr10k -4 years ago 40
Swift Question

How do I write a completion handler for firebase data?

So I had issues previously working with 'observe' from firebase, and I realised I could not bring the variable values from inside the code block that was working asynchronously. A user told me to use completion handlers to resolve this issue, and his example was:

func mapRegion(completion: (MKCoordinateRegion)->()) {

databaseHandle = databaseRef.child("RunList").child(runName).observe(.value, with: { (snapshot) in

let runData = snapshot.value as? [String: AnyObject]
self.minLat = runData?["startLat"] as? Double
self.minLng = runData?["startLong"] as? Double
self.maxLat = runData?["endLat"] as? Double
self.maxLng = runData?["endLong"] as? Double
print("testing")
print(self.minLat!)
print(self.maxLng!)

let region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: (self.minLat! + self.maxLat!)/2,
longitude: (self.minLng! + self.maxLng!)/2),
span: MKCoordinateSpan(latitudeDelta: (self.maxLat! - self.minLat!)*1.1,
longitudeDelta: (self.maxLng! - self.minLng!)*1.1))
completion(region)

})
}


and to use the code:

mapRegion() { region in
mapView.region = region
// do other things with the region
}


So I've tried to recreate this for another method that I need to return an array of object type RunDetail:

func loadRuns(completion: ([RunDetail]) -> ()) {

// we need name, distance, time and user
databaseHandle = databaseRef.child("RunList").observe(.value, with: { (snapshot) in

self.count = Int(snapshot.childrenCount)
print(self.count!)

// more stuff happening here to add data into an object called RunDetail from firebase
// add RunDetail objects into array called 'run'

})

completion(runs)

}


I am not sure if I am setting this up correctly above^.

I still cannot get my head around getting the completion handler working (I really don't understand how to set it up). Can someone please help me and let me know if I am setting this up properly? Thanks.

Answer Source

You need to move the completion(region) to inside the Firebase completion block and add @escaping after completion:.

Also, you should not force unwrap optionals. It is easy enough to check that they are not nil and this will prevent the app from crashing.

func mapRegion(completion: @escaping (MKCoordinateRegion?) -> Void) {

    let ref = Database.database().reference()

    ref.child("RunList").child(runName).observe(.value, with: { (snapshot) in

        guard

            let runData = snapshot.value as? Dictionary<String,Double>,

            let minLat = runData["startLat"],

            let minLng = runData["startLong"],

            let maxLat = runData["endLat"],

            let maxLng = runData["endLong"]

        else {

            print("Error! - Incomplete Data")

            completion(nil)

            return
        }

        var region = MKCoordinateRegion()

        region.center = CLLocationCoordinate2D(latitude: (minLat + maxLat) / 2, longitude: (minLng + maxLng) / 2)

        region.span = MKCoordinateSpanMake((maxLat - minLat) * 1.1, (maxLng - minLng) * 1.1)

        completion(region)
    })
}

Then update your code to this.

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    mapRegion { (region) in

        if let region = region {

            self.mapView.setRegion(region, animated: true)
        }
    }
}

For your loadRuns

func loadRuns(completion: @escaping (Array<RunDetail>) -> Void) {

    let ref = Database.database().reference()

    ref.child("RunList").observe(.value, with: { (snapshot) in

        var runs = Array<RunDetail>()

        // Populate runs array.

        completion(runs) // This line needs to be inside this closure.
    })
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download