19

I'm developing an app and saving some strings like postedAtTime, postedBy, postedOnDate in Firebase database. I want to save the GeoFire coordinates in the same node in which all the above string are saved, so that later I can do query, easily.

Here's the path to which I'm saving all the strings:

databaseReferenceHRequests = firebaseDatabase.getReferenceFromUrl("https://appName-e1a35.firebaseio.com/requests/");

This is how I'm saving it:

// in onButtonClicked method:
postNewRequest(null, imageUID, MainActivity.userName.getText().toString(), time, date, utcFormatDateTime, MainActivity.userEmail.getText().toString(), geoFire);

// the method:
public void postNewRequest(Bitmap bitmap, String imageUIDh, String postedBy, String postedAtTime, String postedOnDate, String utcFormatDateTime, String userEmail, GeoFire geoFire) {
    HRequest hRequest = new HelpRequest(null, imageUIDh, postedBy, postedAtTime, postedOnDate, utcFormatDateTime, userEmail, geoFire);
    databaseReferenceHRequests.push().setValue(hRequest);
}

Here's how it is getting saved in the database:

database structure screenshot

What I want is to save the GeoFire coordinates in the same node, which is -KLIoLUsI0SpQZGpV1h4 here. This is just a push ID and it gets generated randomly.

I tried it by giving this reference:

geoFire = new GeoFire(firebaseDatabase.getReferenceFromUrl("https://appName-e1a35.firebaseio.com/requests/"));

And then pushing it with other items as shown above. But, this saved only GeoFire coordinates and not the other items under the node requests.

So, what should be my GeoFire reference so that it gets saved along with all the data in the same node?

What is going wrong here? Please let me know.

Grimthorr
  • 6,856
  • 5
  • 41
  • 53
Hammad Nasir
  • 2,889
  • 7
  • 52
  • 133

4 Answers4

39

Frank's answer is correct, but I want to give an example. Your database structure should be like this.

{
"items" : {
    <itemId> : {
        "someData" : "someData",
        ...
    }
  },
"items_location" : {
    <itemId> : {
        <geofireData> ...
    }
  }
}

To get the data, first you need to do GeoQuery at items_location node and then get the data on the onKeyEntered method. The parameter key is itemId from my example.

geoFire = new GeoFire(FirebaseDatabase.getInstance().getReference().child("items_location");
geoQuery = geoFire.queryAtLocation(geoLocation), radius);
geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
    @Override
    public void onKeyEntered(String key, GeoLocation location) {
         //retrieve data
    }
};    

Hope this helps.

EDIT How to push the item and set the geofire data.

String itemId = ref.child("items").push().getKey();

ref.child("items").child(itemId).setValue(item);

geoFire = new GeoFire(ref.child("items_location"));
geoFire.setLocation(itemId, new GeoLocation(lattitude, longitude));

EDIT Save the item data and geofire data in one API call

GeoHash geoHash = new GeoHash(new GeoLocation(latitude, longitude));
Map<String, Object> updates = new HashMap<>();
updates.put("items/" + itemId, item);
updates.put("items_location/" + itemId + "/g", geoHash.getGeoHashString());
updates.put("items_location/" + itemId + "/l", Arrays.asList(latitude, longitude));
ref.updateChildren(updates);
philshem
  • 24,761
  • 8
  • 61
  • 127
Wilik
  • 7,630
  • 3
  • 29
  • 35
  • Thanks for your answer. The problem is that my `` is a push id. How can I keep track of it? Please let me know. – Hammad Nasir Jul 13 '16 at 10:55
  • is it necessary to first get the `itemId` and then push data... can't I push data first and then fetch the `itemId` (pushId)? – Hammad Nasir Jul 13 '16 at 11:05
  • looks like there's no other efficient way than this. if you push the data first by calling `ref.push().setValue(item)`, you can't get the push id so it's not possible to do `geoFire.setLocation()`. – Wilik Jul 13 '16 at 11:16
  • Sorry to bring up an old post, but how can I handle the atomicity of both ops ? I mean, I could use the updateChildren, but geofire handles the insertion internally. – Leonardo Dec 09 '16 at 17:50
  • @Leonardo yes, you can. read here [GeoFire.java](https://github.com/firebase/geofire-java/blob/master/src/main/java/com/firebase/geofire/GeoFire.java) at `setLocation(String, GeoLocation, CompletionListener)` method. You should get the idea already. :) – Wilik Dec 09 '16 at 18:21
  • Thanks for the quick reply ! Actually that's not 100% true, because using the callback I can still get some error when inserting the new data, leaving the GeoLocation orphan. – Leonardo Dec 09 '16 at 18:34
  • Thanks, that's what I had in mind. I opened an issue on GeoFire to see if it makes sense to implement something like that out-of-the-box. – Leonardo Dec 11 '16 at 02:39
  • Why do we need to add "/g" or "/l" to that "items_location/" ? – Andy Strife Apr 01 '17 at 00:37
  • Does this mean if I get 50 keys within a certain location query, I will have to do 50 extra separate http calls to get the objects themselves? Seems hugely inefficient... – Wouter May 14 '17 at 12:40
  • @Wouter sadly yes :) – Wilik May 16 '17 at 07:24
  • If I need to save altitude data, it seems I should set it as a property under `items`. Am I correct? – dmr07 Aug 10 '17 at 23:29
  • @dmr07 `GeoLocation` doesn't store altitude, so yes you have to save it under `items` :) – Wilik Aug 11 '17 at 12:36
  • 1
    Follow-up question "Does GeoFire enforce identical keys between item and item_location?": https://stackoverflow.com/q/49379357/2327328 – philshem Mar 20 '18 at 08:25
13

When you use Geofire, you have two lists of data:

  1. a list of items with their regular properties
  2. a list of geohash indexes and their associated keys, which you query through Geofire

You use the keys to get from the Geoquery results to the regular items. That's why the events for Geofire are called "Key Entered", "Key Exited", etc.

Trying to store them in one node is a bad idea, since you're mixing mostly static data (the properties of your items) with highly volatile data (the geo-location information). Separating the two out leads to better performance, which is why Geofire enforces it.

While there may be use-cases where the properties and geo-data are equally dynamic/static, GeoFire does not support keeping the geo-data and other properties in a single location.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thanks for answering, but I am not sure I got the answer to my question. I mean how would I be able to retrieve only those posts from database which is within 0.5 km radius of the user? I'm also saving the latitude and longitude from where the posts were posted. Please make it more clear, sir. – Hammad Nasir Jul 13 '16 at 01:26
  • Does Geofire return just the keys from the node and then you have to query the regular properties node. Or does Geofire also return the regular properties data? – Barry MSIH Oct 05 '16 at 23:20
  • @Frank can you please explains how to store geohash indexes and what will be its structurre – Muhammad Younas Jan 24 '18 at 06:17
1

You can use Firebase functions to enter it for you on every new entry

let functions = require('firebase-functions');

let GeoFire = require('geofire');

exports.testLocation = functions.database.ref('/items/{item}').onWrite(event => {
let data = event.data.val();
   console.log(data);
   console.log(event.params.item);

   if (data.location && data.location.coords) {

       console.log('Update GeoFire');

       let ref = event.data.adminRef.parent.parent.child('/items_locations'));

       let key = event.params.test;
       let location = [data.location.coords.latitude, data.location.coords.longitude]);
       let geoFire = new GeoFire(ref);

       geoFire.set(key, location).then(() => {
          console.log('Update succesfull');
       }).catch(error => {
          console.log(error);
       });
    }
}
Jonas Tomanga
  • 1,069
  • 10
  • 19
1

For those more recently coming to this post with the same question, this is possible with the Geofirestore library, which supports Geofire for apps built on top of the Firebase Firestore database.

DHShah01
  • 553
  • 1
  • 7
  • 16