Geofire/Firebase Function Is Executing Handler Multiple Times in Swift

GeoFire observeReady gets executed prematurely in Swift

What you're seeing is expected behavior. The observeReady is guaranteed to fire after all the corresponding observe(.keyEntered) have been called. You can verify this with some simple logging statements:

query.observe(.keyEntered) { (key: String!, venueLocation: CLLocation!) in 
print(".keyEntered")
}
query.observeReady {
print(".observeReady")
}

When you run this it will print:

.keyEntered

.keyEntered

...

.observeReady

That is in line with how the API is supposed to work. But in the .keyEntered you are loading additional data from Firebase, which happens asynchronously. And those calls may indeed complete after the .observeReady has fired.

So you will need to implement the necessary synchronization yourself. A simple way to detect if you have loaded all the additional data, is to keep a count of all the keys for which you still need to load data. So you +1 that every time you add a key, and -1 every time you've loaded the venue data:

let venuesToLoadCount = 0
query.observe(.keyEntered) { (key: String!, venueLocation: CLLocation!) in
venuesToLoadCount = venuesToLoadCount + 1
self.REF_VENUES.child(key).observeSingleEvent(of: .value, with: { (snapshot) in
venuesToLoadCount = venuesToLoadCount - 1
if venuesToLoadCount == 0 {
print("All done")
}
}
}
query.observeReady {
if venuesToLoadCount == 0 {
print("All done")
}
}

Firebase & GeoFire fetch double adds new post to collection view

When you call circleQuery.observe(.keyEntered, it starts an observer. This will continue to observe for keys entering the queried range, until you remove the observer. Since you never remove the observer, the second time you call fetchPosts you're actually adding a second observer, and that means your code will be called twice for each key that is in range.

define the handle and query in your class so you can then access it outside of the fetchPosts function

var handle: DatabaseHandle!
var circleQuery: GFCircleQuery!

To remove the observer, make sure you keep the handle that is returned when you attach the observer:

circleQuery = geoFire.query(at: myLocation, withRadius: 10)

self.handle = circleQuery?.observe(.keyEntered, with: { key, location in

And then remove the observer by calling in fetchPosts completion Handler:

func refreshCollectionView() {
circleQuery?.removeObserver(withFirebaseHandle: handle)
self.collectionView.reloadData()
}

If you're only looking for objects in the range of the query when you call fetchPosts and don't want to keep observing, you'll typically remove the observer once GeoFire has called you for all initial objects, which is when it is 'ready' with the query:

circleQuery.observeReadyWithBlock({
print("All initial data has been loaded and events have been fired!")
circleQuery.removeObserverWithHandle(handle)
})

Swift: Geofire Instanciation error after Firebase 3 upgrade

GeoFire needs to be updated to version 2 also in order to make it work with Firebase 3.
Follow the instructions below to fix this issue:

  1. Download zip source from here: https://github.com/firebase/geofire-objc
  2. Unzip it and go to /GeoFire/API folder
  3. Copy all 4 files in it
  4. Go to your project and navigate to your GeoFire.framework
  5. Navigate to Headers folder, you will see the same 4 files there
  6. Replace them
  7. Clean your project, it is now fixed.

Please note that as of today there is no pod available for GeoFire 2

How to get Documents out of an geo query?

I don't know how your documents are structured or how your map is configured to display data (annotations versus regions, for example), but the general fix for your problem is to coordinate the loop of queries in your function and give them a completion handler. And to do that, we can use a Dispatch Group. In the completion handler of this group, you have an array of document snapshots which you need to loop through to get the data (from each document), construct the Pin, and add it to the map. There are a number of other steps involved here that I can't help you with since I don't know how your documents and map are configured but this will help you. That said, you could reduce this code a bit and make it more efficient but let's just go with the Firebase sample code you're using and get it working first.

struct Pin: Identifiable {
let id = UUID().uuidString
var location: MKCoordinateRegion
var name: String
var img: String
}

func geoQuery() {
// [START fs_geo_query_hashes]
// Find cities within 50km of London
let center = CLLocationCoordinate2D(latitude: 51.5074, longitude: 0.1278)
let radiusInKilometers: Double = 50

// 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: radiusInKilometers)
let queries = queryBounds.compactMap { (Any) -> Query? in
guard let bound = Any as? GFGeoQueryBounds else { return nil }
return db.collection("cities")
.order(by: "geohash")
.start(at: [bound.startValue])
.end(at: [bound.endValue])
}

// Create a dispatch group outside of the query loop since each iteration of the loop
// performs an asynchronous task.
let dispatch = DispatchGroup()

var matchingDocs = [QueryDocumentSnapshot]()
// 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))")
dispatch.leave() // leave the dispatch group when we exit this completion
return
}

for document in documents {
let lat = document.data()["lat"] as? Double ?? 0
let lng = document.data()["lng"] as? Double ?? 0
let name = document.data()["names"] as? String ?? "no name"
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 <= radiusInKilometers {
matchingDocs.append(document)
}
}
dispatch.leave() // leave the dispatch group when we exit this completion
}

// 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 {
dispatch.enter() // enter the dispatch group on each iteration
query.getDocuments(completion: getDocumentsCompletion)
}
// [END fs_geo_query_hashes]

// This is the completion handler of the dispatch group. When all of the leave()
// calls equal the number of enter() calls, this notify function is called.
dispatch.notify(queue: .main) {
for doc in matchingDocs {
let lat = doc.data()["lat"] as? Double ?? 0
let lng = doc.data()["lng"] as? Double ?? 0
let name = doc.data()["names"] as? String ?? "no name"
let coordinates = CLLocation(latitude: lat, longitude: lng)
let region = MKCoordinateRegion(center: <#T##CLLocationCoordinate2D#>, latitudinalMeters: <#T##CLLocationDistance#>, longitudinalMeters: <#T##CLLocationDistance#>)
let pin = Pin(location: region, name: name, img: "someImg")

// Add pin to array and then to map or just add pin directly to map here.
}
}
}


Related Topics



Leave a reply



Submit