Search code examples
javagoogle-cloud-firestorefirebase-admingeofiregeofirex

Make geoQuery.addGeoQueryEventListener() a blocking operation


I have saved a list of shops with some dummy data and latitude/longitudes for each document in Firestore. I want to get documents nearby a poi. For that purpose I have used GeoFire library. Now, I am able to sort data inside from geoQuery.addGeoQueryEventListener() method but this method is not a blocking operation so my code is exiting before geoQuery.addGeoQueryEventListener() could return sorted list. Below is the code snippet:

    List<QueryDocumentSnapshot> doc = new ArrayList<>();
    List<String> businessIds = new ArrayList<>();
    Set<Business> businessSet = new HashSet<>();

    GeoQuery geoQuery = geoFire.queryAtLocation(new GeoLocation(latitude, longitude), 2000);
    geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {

        @Override
        public void onKeyEntered(String key, GeoLocation location) {
            // TODO Auto-generated method stub
            businessIds.add(key);
        }

        @Override
        public void onKeyExited(String key) {
            // TODO Auto-generated method stub
        }

        @Override
        public void onKeyMoved(String key, GeoLocation location) {
            // TODO Auto-generated method stub
        }

        @Override
        public void onGeoQueryReady() {
            // TODO Auto-generated method stub
            System.out.println("All initial data has been loaded and events have been fired!");
        }

        @Override
        public void onGeoQueryError(DatabaseError error) {
            // TODO Auto-generated method stub
            System.err.println("There was an error with this query: " + error);
        }

    });
    geoQuery.removeAllListeners();

    ApiFuture<QuerySnapshot> query = firestore.collection("business").whereIn("businessId", businessIds).get();
    businessSet = query.get().getDocuments().stream()
            .map(b -> b.toObject(Business.class)).collect(Collectors.toSet());
    return businessSet;

In the above code, My program exits before key could be added to businessIds in onKeyEntered().


Solution

  • Note that I don't know those APIs you're working with, but theoretically, you should be able to play with CompletableFuture, for instance with something like this (not tested):

    private CompletableFuture<List<String>> runGeoQuery(double latitude, double longitude) {
      CompletableFuture<List<String>> future = new CompletableFuture<>();
      GeoQuery geoQuery = geoFire.queryAtLocation(new GeoLocation(latitude, longitude), 2000);
      geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
    
        private final List<String> businessIds = new ArrayList<>();
    
        @Override
        public void onKeyEntered(String key, GeoLocation location) {
          businessIds.add(key);
        }
    
        @Override
        public void onKeyExited(String key) {
        }
    
        @Override
        public void onKeyMoved(String key, GeoLocation location) {
        }
    
        @Override
        public void onGeoQueryReady() {
          future.complete(businessIds);
        }
    
        @Override
        public void onGeoQueryError(DatabaseError error) {
          future.completeExceptionally(error);
        }
    
      });
      return future;
    }
    

    Then, you can either compose on the result with #thenApply or #thenCompose, or you can simply block with #get.

    Side note, just make sure blocking is fine in your case. One example where it should not be fine, is when the thread running this code comes from an HTTP request pool (e.g. Netty's), as this might create contention and stop your web server handling new requests.