Assemble a list of users with Geofire/Firebase
The data from Geofire and the rest of your Firebase Database is not simply "gotten" from the database. It is asynchronously loaded and then continuously synchronized. This changes the flow of your code. This is easiest to see by adding some logging:
func getNearbyTrucks(){
//Query GeoFire for nearby users
//Set up query parameters
let center = CLLocation(latitude: 37.331469, longitude: -122.029825)
let circleQuery = geoFire.queryAtLocation(center, withRadius: 100)
print("Before Geoquery")
circleQuery.observeEventType(GFEventTypeKeyEntered, withBlock: { (key: String!, location: CLLocation!) in
print("In KeyEntered block ")
let newTruck = Truck()
newTruck.id = key
newTruck.currentLocation = location
self.nearbyTrucks.append(newTruck)
}) //End truckQuery
print("After Geoquery")
}
The output of the logging will be in a different order from what you may expect:
Before Geoquery
After Geoquery
In KeyEntered block
In KeyEntered block
...
While the Geo-keys and users are being retrieved from the server, the code continues and getNearbyTrucks()
exits before any keys or users are returned.
One common way to deal with this is to change the way you think of your code from "first load the trucks, then print the firs truck" to "whenever the trucks are loaded, print the first one".
In code this translates to:
func getNearbyTrucks(){
//Query GeoFire for nearby users
//Set up query parameters
let center = CLLocation(latitude: 37.331469, longitude: -122.029825)
let circleQuery = geoFire.queryAtLocation(center, withRadius: 100)
circleQuery.observeEventType(GFEventTypeKeyEntered, withBlock: { (key: String!, location: CLLocation!) in
let newTruck = Truck()
newTruck.id = key
newTruck.currentLocation = location
self.nearbyTrucks.append(newTruck)
print(nearbyTrucks[0].id)
}) //End truckQuery
//Execute code once GeoFire is done with its' query!
circleQuery.observeReadyWithBlock({
for truck in self.nearbyTrucks{
ref.childByAppendingPath("users/\(truck.id)").observeEventType(.Value, withBlock: { snapshot in
print(snapshot.value["name"] as! String)
truck.name = snapshot.value["name"] as! String
truck.description = snapshot.value["selfDescription"] as! String
let base64String = snapshot.value["profileImage"] as! String
let decodedData = NSData(base64EncodedString: base64String as String, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)
truck.photo = UIImage(data: decodedData!)!
})
}
}) //End observeReadyWithBlock
}
I've moved the printing of the first truck into the block for the key entered event. Depending on the actual code you're trying to run, you'll move it into different places.
A more reusable approach is the one the Firebase Database and Geofire themselves use: you pass a block into observeEventType withBlock:
and that block contains the code to be run when a key is available. If you apply the same pattern to you method, it'd become:
func getNearbyTrucks(withBlock: (key: String) -> ()){
//Query GeoFire for nearby users
//Set up query parameters
let center = CLLocation(latitude: 37.331469, longitude: -122.029825)
let circleQuery = geoFire.queryAtLocation(center, withRadius: 100)
circleQuery.observeEventType(GFEventTypeKeyEntered, withBlock: { (key: String!, location: CLLocation!) in
let newTruck = Truck()
newTruck.id = key
newTruck.currentLocation = location
self.nearbyTrucks.append(newTruck)
withBlock(nearbyTrucks[0].id)
}) //End truckQuery
//Execute code once GeoFire is done with its' query!
circleQuery.observeReadyWithBlock({
for truck in self.nearbyTrucks{
ref.childByAppendingPath("users/\(truck.id)").observeEventType(.Value, withBlock: { snapshot in
print(snapshot.value["name"] as! String)
truck.name = snapshot.value["name"] as! String
truck.description = snapshot.value["selfDescription"] as! String
let base64String = snapshot.value["profileImage"] as! String
let decodedData = NSData(base64EncodedString: base64String as String, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)
truck.photo = UIImage(data: decodedData!)!
})
}
}) //End observeReadyWithBlock
}
Here again, you'll want to move the withBlock()
callback to a more suitable place depending on your needs.
Retrieve a list of users based on distance with geofire & firebase
Try the following:
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("GeoFire");
ref.orderByChild("lattitude").equalTo(lattitude_here).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot datas: dataSnapshot.getChildren()){
String userID=datas.getKey();
}
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
});
Here the snapshot is at GeoFire
, then you add a query and iterate to be able to retrieve the ids according to the result of the query.
Retrieving list of users only within 3 miles using geofire
I solved it with the Help of answers from Frank Van Puffelen. Here's what I did
public void onKeyEntered(final String key, GeoLocation location) {
firebaseDatabase.child(key).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
Users my=dataSnapshot.getValue(Users.class);
list.add(my);
adapter=new MyAdapter(MainActivity.this,list);
recycler.setAdapter(adapter);
}
getting users on my radius via geofire to a list and rules
The message is pretty explicit:
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"Geofire": {
".indexOn": "g"
}
}
}
GeoFire and Firebase useage to locate nearest user
The problem is, you're referencing the geofire to the root node. You should reference it to a special node for geofire.
What your database structure should look like
"User": {
"uid1": {
"Availability": true,
},
"uid2": {
"Availability": true,
}
},
"User_Location": {
"uid1": {
.. some geofire data ..
},
"uid2": {
.. some geofire data ..
}
}
How to reference the geofire to another location
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("User_Location");
GeoFire geoFire = new GeoFire(ref);
Then set the location as usual
geoFire.setLocation(userId, new GeoLocation(userLatitude, userLongitude));
After that, the Geofire query should work fine.
GeoFire Query on lists of data
I think you need to separate your GeoFire from your UserDatabase
To Set Locations call
geoFire.setLocation(CLLocation(latitude: 37.7853889, longitude: -122.4056973), forKey: **UserID** )
Then you create the Circle Query
let center = CLLocation(latitude: 37.7832889, longitude: -122.4056973)
var circleQuery = geoFire.queryAtLocation(center, withRadius: 0.6) // 600m
the you make the call against the database
var queryHandle = query.observeEventType(.KeyEntered, withBlock: { (key: String!, location: CLLocation!) in
print("Key '\(key)' entered the search area and is at location '\(location)'")
})
Every Key that is returned will be the UserID for your datebase and you can access the relevant path through a /User/{UserID}/...
Related Topics
How to Have Arview and Arscnview Coexist
Need Help Converting (Cfpropertylistref *)Nsdictionary to Swift
How to Generate an Auth Token Using Jwt for Google Firebase
How to Declare Protocol Property as Private
Convert JSON Anyobject to Int64
Swift 3:Appdelegate Does Not Conform to Protocol Gidsignindelegate
How to Check If Cmtime Is Valid in Swift
How to Set Realtime Thread in Swift
Require Associatedtype to Be Representable in a @Convention(C) Block
How to Create a Multiline Textfield in Swiftui? Like the Notes App
Nskeyedunarchiver Fails to Decode a Custom Object in Swift
Xcode 8 Does Full Project Rebuild
How to Give PDF Data a Filename for User to Save in Swift
Swift Get Nsdata of a Video from Photos Library