Search code examples
swiftfirebasegoogle-cloud-firestoregeofire

Firebase GeoFire closure


I'm trying Firebase GeoFire and closures always trip me up. Firebase provides this sample code snippet in their documentation

// After all callbacks have executed, matchingDocs contains the result. Note that this
// sample does not demonstrate how to wait on all callbacks to complete.
for query in queries {
    query.getDocuments(completion: getDocumentsCompletion)
}

Unfortunately it doesn't demonstrate just what I need...

I want this to fire on pre set interval so I have a scheduled it like this:

locationTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(getLocations), userInfo: nil, repeats: true)

Then my code is pretty much as per the sample:


var matchingDocs = [QueryDocumentSnapshot]()

@objc func getLocations() {
        let center = CLLocationCoordinate2D(latitude: lat, longitude: lon)
        let radiusInM: Double = 10000

        // Each item in 'bounds' represents a startAt/endAt pair. We have to issue
        // a separate query for each pair. There can be up to 9 pairs of bounds
        // depending on overlap, but in most cases there are 4.
        let queryBounds = GFUtils.queryBounds(forLocation: center,
                                              withRadius: radiusInM)
        let queries = queryBounds.map { bound -> Query in
            return db.collection("geopoints")
                .order(by: "geohash")
        }
        
        // Collect all the query results together into a single list
        func getDocumentsCompletion(snapshot: QuerySnapshot?, error: Error?) -> () {
            guard let documents = snapshot?.documents else {
                print("Unable to fetch snapshot data. \(String(describing: error))")
                return
            }

            for document in documents {
                let lat = document.data()["lat"] as? Double ?? 0
                let lng = document.data()["lng"] as? Double ?? 0
                let coordinates = CLLocation(latitude: lat, longitude: lng)
                let centerPoint = CLLocation(latitude: center.latitude, longitude: center.longitude)

                // We have to filter out a few false positives due to GeoHash accuracy, but most will match
                let distance = GFUtils.distance(from: centerPoint, to: coordinates)
            
                if distance <= radiusInM {
                    matchingDocs.append(document)
                }
            }
        }

        // After all callbacks have executed, matchingDocs contains the result. Note that this
        // sample does not demonstrate how to wait on all callbacks to complete.
        for query in queries {
            query.getDocuments(completion: getDocumentsCompletion)
        }
    }
}

What is the proper way to wait for the callbacks to complete, and continue executing the program outside the getLocations() function with matchingDocs containing all the geopoints? Thanks for any recommendations.


Solution

  • This is my solution. Function allDone() with the completion will be called only after all queries come back.

    // button press to read data from Firebase
    @IBAction func getData(_ sender: Any) {
        getLocations(completion: {
               
        // code here to process all query results        
                
        })
    }
    
    func getLocations(completion: @escaping () -> Void) {
       let center = CLLocationCoordinate2D(latitude: lat, longitude: lon)
       let radiusInM: Double = 10000
            
       var queriesFinished = 0
            
       // Each item in 'bounds' represents a startAt/endAt pair. We have to issue
       // a separate query for each pair. There can be up to 9 pairs of bounds
       // depending on overlap, but in most cases there are 4.
       let queryBounds = GFUtils.queryBounds(forLocation: center,
                                             withRadius: radiusInM)
       let queries = queryBounds.map { bound -> Query in
           return db.collection("geopoints")
               .order(by: "geohash")
       }
            
       let numOfQueries = queries.count
            
       // Collect all the query results together into a single list
       func getDocumentsCompletion(snapshot: QuerySnapshot?, error: Error?) -> () {
           guard let documents = snapshot?.documents else {
               print("Unable to fetch snapshot data. \(String(describing: error))")
               return
           }
                
           for document in documents {
               let lat = document.data()["lat"] as? Double ?? 0
               let lng = document.data()["lng"] as? Double ?? 0
               let coordinates = CLLocation(latitude: lat, longitude: lng)
               let centerPoint = CLLocation(latitude: center.latitude, longitude: center.longitude)
    
               // We have to filter out a few false positives due to GeoHash accuracy, but most will match
              let distance = GFUtils.distance(from: centerPoint, to: coordinates)
                
               if distance <= radiusInM {
                   matchingDocs.append(document)
               }
           }
                
           queriesFinished += 1
                
           allDone()
       }
            
       // After all callbacks have executed, matchingDocs contains the result. Note that this
       // sample does not demonstrate how to wait on all callbacks to complete.
       for query in queries {
           query.getDocuments(completion:getDocumentsCompletion)
       }
            
       func allDone() {
           if queriesFinished == numOfQueries {
               completion()
           }
       }
    }
    

    I was reading that async/await may be a better way but did not get it to work.