0

I'm newbie with Firebase + GeoFire and I'm having trouble with geoFire query function.

I want to add in an array the results from geoQuery function and return it in a function. But the data I have manipulating inside geoQuery.on method seems out of scope or not available or due to promises, I dont know... the fact is outside the geoquery.on method the variable sellers is empty.

How can I return results from geoQuery and save it into a return variable

//Set seller position in firebase db
var setPosition = function() {
    navigator.geolocation.getCurrentPosition(setPositionSuccess, error, options);
    //navigator.geolocation.watchPosition(setPositionSuccess, positionError, { enableHighAccuracy:true })
};  

//Get sellers near buyer position
var getSellersForCurrentPosition = function() {
    navigator.geolocation.getCurrentPosition(getPositionSuccess, error, options);
    //navigator.geolocation.watchPosition(positionSuccess, positionError, { enableHighAccuracy:true })
};  


//Callback function from html 5 geo api
function getPositionSuccess(pos) {

    var crd = pos.coords;
    var currentPosition = [crd.latitude, crd.longitude];

    // Query radius
    var radiusInKm = 2;

    var firebaseRef = new Firebase(FBURL + "/geofire/sellers/");
    var geoFire = new GeoFire(firebaseRef);

    var geoQuery = geoFire.query({
        center: currentPosition,
        radius: radiusInKm
    }); 

    var sellers = []; 
    var oneSeller = {}; 

    var onKeyEnteredRegistration = geoQuery.on("key_entered", function(key, location, distance) {
        oneSeller = { 
            id: key,
            distance: distance,
            location: location
        };  
        sellers.push(oneSeller);
    }); 

    var onReadyRegistration = geoQuery.on("ready", function() {
          geoQuery.cancel();
    });

    return sellers;

} 

By the way, how accurate is html5 geolocation? Is it different between desktop browser and mobile browser?

Lorraine
  • 441
  • 5
  • 23

1 Answers1

0

Geofire monitors the sellers in the range you indicate. Any time a seller enters/exits the range, it fires a key_entered or key_exited event. These events can happen at any time after you start the query. In JavaScript terms this is often described as: the callbacks happen asynchronously.

A simple event flow, might explain what happens best:

  1. you call getPositionSuccess()
  2. you start a Geoquery to monitor the sellers that are in range: geoFire.query()
  3. no sellers are immediately in range, so your callback doesn't fire
  4. the getPositionSuccess() function is done and exits
  5. a seller comes in range
  6. GeoFire fires the key_entered event and your callback runs
  7. but getPositionSuccess() has already exited, so how can it return a value?

Even if you were to wait for the first seller to come into range before returning (not possible in a browser, but it is possible in other languages/environments), how will you return the value when a second seller comes in range?

For this reason, you have to deal with asynchronous data differently. Typically you do this by moving the code that would call the getPositionSuccess() function into the function.

Say you are now trying to do this:

var sellers = getPositionSuccess(pos);
sellers.forEach(function(seller) {
  addSellerToMap(seller);
});

To handle the asynchronous nature of the events, you'd move this code into getPositionSuccess:

//Callback function from html 5 geo api
function getPositionSuccess(pos) {
    var crd = pos.coords;
    var currentPosition = [crd.latitude, crd.longitude];

    // Query radius
    var radiusInKm = 2;

    var firebaseRef = new Firebase(FBURL + "/geofire/sellers/");
    var geoFire = new GeoFire(firebaseRef);

    var geoQuery = geoFire.query({
        center: currentPosition,
        radius: radiusInKm
    }); 

    var oneSeller = {}; 

    geoQuery.on("key_entered", function(key, location, distance) {
        oneSeller = { 
            id: key,
            distance: distance,
            location: location
        };  
        addSellerToMap(oneSeller);
    }); 
} 

I understand that in your use-case your sellers won't move, so it may be more intuitive to think of them as a static list. But even in this case, the results are loaded from a remote database and it will take some time before that data is loaded. The modern web loads data asynchronously and all your code will have to deal with it in a way similar to what I outlined above.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thanks for your answer. At first, I have dealing with static sellers to do it more easily but the idea is that they are in movement, so I will use watchPosition html5 geo api method. So, let's say I have a node with sellers and their positions being updated in firebase db. Then, a buyer makes a request, html5 api takes its current position and geoQuery shows sellers who have close. I will not add they on a map, just get their ID's. So I need to return an array. Thanks and sorry for my english :{ – Lorraine Jul 19 '15 at 16:08
  • All sounds like a perfectly valid use-case. But unfortunately: the modern web in general and Firebase in particular are asynchronous in nature. You *cannot* synchronously return data that is loaded asynchronously. All you can do is update a central location and then let the code that needs to know about the updated data know about it. So where it says `addSellerToMap` you can also put `tellBuyerThatASellerWasFound`. – Frank van Puffelen Jul 19 '15 at 16:51
  • You will have to come to grips with asynchronous programming. For a good answer that covers it in a slightly different context: http://stackoverflow.com/questions/27049342/asynchronous-access-to-an-array-in-firebase/27050749#27050749. For a primer not related to Firebase, but equally relevant, read: http://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-asynchronous-call?lq=1 – Frank van Puffelen Jul 19 '15 at 16:54
  • Thanks for the reply and the links, but I think my problem is easier. Look at the first answer in this link, https://groups.google.com/forum/#!topic/firebase-talk/VUdy5yAhN1k this is just what I want, but sellers variable is empty outside geoQuery.on method, should we use a $scope variable? In geoQuery.on("ready", callback) I get sellers variable but i need make a return to load $scope.sellers variable in the SellerCtrl controller – Lorraine Jul 19 '15 at 17:43