1- All the users in my app send their location to GeoFire
every 2.5 minutes using a Timer
.
2- Other users also query GeoFire to look for whichever users are in a 1 mile radius from them (eg. 10 users). I get those 10 users and then and add them to an array.
3- I then loop through the array with those 10 users using their userId (the geoRef key). I go to their Database ref and search to see if they fit some criteria. If they do I add them to a different array (eg 5 users are now in this subSet array)
4- Since every 2.5 minutes every user's location is sent to GeoFire
, that means those 5 users in that subset might have a different location from when they were first added to the subset array.
I can use a timer to query the location on those 5 users. The question is how do I query GeoFire to get the location of each individual user from only those 5 users? I don't want query everyone within that 1 mile region again otherwise it's going to get me those same 10 users
// I have a Timer firing this off
func queryLocationOfSubsetOfUsersInRadius() {
let geofireRef = Database.database().reference().child("geoLocations")
let geoFire = GeoFire(firebaseRef: geoFireRef)
let dispatchGroup = DispatchGroup()
for user in subsetOfUsersInRadius {
dispatchGroup.enter()
let userId = user.userId
// I don't know if this is the right method to use. I only added it here because I saw it has observeValue on it
geoFire.observeValue(forKeyPath: userId, of: Any?, change: NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
// *** HOW TO GET THE USERS NEW LOCATION and use dispatchGroup.leave() as each one is obtained??? ***
dispatchGroup.leave()
}
dispatchGroup.notify(queue: .global(qos: .background)) {
// now animate the annotation from the user's inital old location (if they moved) on the mapView to their new location on the mapView. It's supposed to look like Uber's cars moving. Happens on main thread
}
}
Supporting code below
var queryHandle: UInt?
var regionQuery: GFRegionQuery?
var usersInRadius = [User]() // has 10 users
var subsetOfUsersInRadius = [User]() // of the 10 only 5 fit some criteria
let geofireRef = Database.database().reference().child("geoLocations")
let geoFire = GeoFire(firebaseRef: geofireRef)
// region: MKCoordinateRegion was previously set at 1 mile 1609.344 meters
regionQuery = geoFire.query(with: region)
queryHandle = regionQuery?.observe(.keyEntered, with: { [weak self](key: String!, location: CLLocation!) in
let user = User()
user.userId = key
user.location = location
self?.usersInRadius.append(user)
})
regionQuery?.observeReady({ [weak self] in
self?.sortUsersInRadius(arr: self!.usersInRadius)
})
func sortUsersInRadius(arr: [User]) {
if let queryHandle = queryHandle {
regionQuery?.removeObserver(withFirebaseHandle: queryHandle)
}
let dispatchGroup = DispatchGroup()
for user in arr {
let userId = user.userId
someDatabaseRef.child(userId).observeSingleEvent(of: .value, with: { (snapshot) in
// if snapshot contains some critera add that user to the subSet array
self?.subsetOfUsersInRadius.append(user) // only 5 users fit this criteria
dispatchGroup.leave()
})
}
dispatchGroup.notify(queue: .global(qos: .background)) {
// add an annotation to mapView to show the initial location of each user from subsetOfUsersInRadius. Happens on main thread
}
}
Inside the Database the GeoFire ref of each userId location has a "g"
child and a "l"
child:
@geoLocations
|
@--abc123xyz456 // userId
|
@--g: "dr72xyz25abc" // geoFire id for this user's location in geoFire
|
@--l
|--0: 40.870431300779900 // latitude
|--1: -73.090007211987188 // longitude
Here's a picture of the actual database layout
I have no idea what "g"
stands for but I assume "l"
stands for location because it's of type CLLocation
as is stated in the arguments of .observe(.keyEntered, with: { (key: String!, location: CLLocation!)
.
Inside the database the 0
key and 1
are held as snapshot.value
of array of either CLLocationDegrees
or Double
.
To get the latitude
and longitude
I used let arr = snapshot.value as? [CLLocationDegrees]
but let arr = snapshot.value as? [Double]
also worked.
Create a ref that has a child name of whatever the name of your geoLocations ref is > then add a child of the the userId > then add a child of "l" for the locations child.
Run observeSingleEvent(of: .value
and in the callback cast snapshot.value
as an an array of [CLLocationDegrees]
// *** if using CLLocationDegrees be to import CoreLocation ***
import CoreLocation
let geoLocationsRef = Database.database().reference()
.child("geoLocations") // name of my geoRef in Firebase
.child("abc123xyz456") // the userId I'm observing
.child("l") // the "l" is the child to observe
geoLocationsRef.observeSingleEvent(of: .value, with: { (snapshot) in
if !snapshot.exists() { return }
guard let arr = snapshot.value as? [CLLocationDegrees] else { return }
if arr.count > 1 {
let latitude = arr[0]
print(latitude)
let longitude = arr[1]
print(longitude)
// do whatever with the latitude and longitude
}
})
Here is the answer to my question with using dispatchGroup():
func queryLocationOfSubsetOfUsersInRadius() {
let dispatchGroup = DispatchGroup()
for user in subsetOfUsersInRadius {
dispatchGroup.enter()
let userId = user.userId
let geoLocationsRef = Database.database().reference()
.child("geoLocations")
.child(userId)
.child("l")
geoLocationsRef.observeSingleEvent(of: .value, with: { (snapshot) in
// this user may have deleted their location
if !snapshot.exists() {
dispatchGroup.leave()
return
}
guard let arr = snapshot.value as? [CLLocationDegrees] else {
dispatchGroup.leave()
return
}
if arr.count > 1 {
let latitude = arr[0]
print(latitude)
let longitude = arr[1]
print(longitude)
// do whatever with the latitude and longitude
}
dispatchGroup.leave()
})
}
dispatchGroup.notify(queue: .global(qos: .background)) {
// now animate the annotation from the user's inital old location (if they moved) on the mapView to their new location on the mapView. It's supposed to look like Uber's cars moving. Happens on main thread
}
}