Wednesday, June 3, 2015

Adding and removing GeoFences to a Google Map

Problem:

There is no native way to add, keep track of, or erase GeoFences that are added to a Google Map.

Solution:

After a GeoFence is successfully added to the GeoFencing API, use its radius, latitude, and longitude to construct a Circle object. Add this Circle to a Stack of Circles, and also add it to the Google Map object. Pop the circle off the stack and remove it from the Map when removing the GeoFence.

Explanation:

This assumes you have a GeoFencing API already set up and a Google Map set up.
If you followed the Android GeoFence tutorial you should end up with something like this:

LocationServices.GeofencingApi.addGeofences(
    mGoogleApiClient,
    getGeofencingRequest(),
    getGeofencePendingIntent()
).setResultCallback(this);

In my code I call this every time a GeoFence is added. If that completes successfully, you can now add your GeoFence to the map. You'll need to know the fence's ID, latitude, longitude, and radius, so make sure to store those somewhere. Then, you're going to want to call your method that will do the work for making the circle with the map. I called mine createCircle() and I call it like this. Latlng is a LatLng type, and rad is a float.

createCircle(new CircleOptions()
    .center(latlng)
    .radius(rad)
    .strokeColor(Color.BLACK));

Here is the method that does the work:

private void createCircle(CircleOptions circleOptions){
    if(mMap != null) {
        Circle newCircle = mMap.addCircle(circleOptions);
        if(newCircle != null) {
            circleStack.push(newCircle);
        } else {
            Log.d(TAG, "newCircle is null");
        }
    } else {
        Log.d(TAG, "mMap is null");
    }
}

mMap is an instance variable of the Google Map that was created earlier in our code. Make sure you only call this after the Map is ready. Remember that Google Maps has the onMapReady() callback method, and this should be in your code already if you implemented OnMapReadyCallback.

circleStack is a stack that I instantiated like this:

private Stack<Circle> circleStack = new Stack<Circle>();

Ta-da! Now we have a circle on our map exactly where the GeoFence is!
And the Stack keeps track of the Circles for when we want to remove them.

Removing Circles and GeoFences:

The Android GeoFence tutorial only shows you how to remove all the geoFences for a pending intent. But there is another removeGeofences() method that takes a different argument and allows you to remove a list of fences based on their IDs. But how do you remove just a single GeoFence? I solved this by adding a button that would remove the most recently added GeoFence from the Geofencing Api and from the Google Map.

To remove the GeoFence from the API, just get the most recently added GeoFence from your ArrayList of GeoFences, which if you followed the Android GeoFence tutorial is called mGeofenceList. The most recently added fence will be at the highest index of your mGeofenceList. So get the fence from the highest index, and then get its ID and save it in a variable. You'll also want to remove from the mGeofenceList.
Then, add that ID to a List<String>. Yes, it seems a bit ridiculous to add a single entry to a List, but we need to use a List with the removeGeofences method. Now we are ready to call GeofencingApi.removeGeofences(), passing it your mGoogleApiClient (the same one from above) and your List of a single GeoFence ID to remove.

Finally, remove the Circle from the map. This is where the stack comes in. Pop the circle from the stack and store it in a Circle variable, and then call remove() on that Circle. Note that pop returns the object that it popped off the top of the stack.

Now your GeoFences are removed from the API, so they won't do anything if you enter, exit, or dwell within them. They are also removed from the map so no one will think a GeoFence exists where it doesn't.

public void removeGeofencesClick(View view) {
    if(mGeofenceList.size() > 0 && mGoogleApiClient.isConnected()) {
        int mGeofenceListLastIndex = mGeofenceList.size() - 1;
        Geofence fenceToRemove = mGeofenceList.get(mGeofenceListLastIndex);
        String geoFenceIdToRemove = fenceToRemove.getRequestId();
        mGeofenceList.remove(mGeofenceListLastIndex);

        //This will only ever hold 1 fence at a time, but removeGeofences()
        //takes a List so that's why we make a List.
        List<String> geoFenceToRemove = new ArrayList<>();
        geoFenceToRemove.add(geoFenceIdToRemove);
        LocationServices.GeofencingApi.removeGeofences(mGoogleApiClient,
            geoFenceToRemove).setResultCallback(this);
        //This removes the last added GeoFence from the Map,
        // but not the GeoFence collection.
        if(!circleStack.empty()){
            Circle circleToRemove = (Circle)circleStack.pop();
            circleToRemove.remove(); //remove it from the Map
        }
    }
}

3 comments:

  1. Lets assume i had created 10 geofences on the map. Is there any way to delete the fifth geofence?
    I am not sure if i got your point but it seems from your answer that we can either delete all the geofences or just the one that is added at the end.
    I would really appreciate your answer if u do so

    ReplyDelete
    Replies
    1. Hi Adnan,
      Yes there is a way to delete any of the geofences. I dug into this code that I have not touched in a couple of years to get some answers for you.

      What I did is create a new class that implements Geofence. I then gave that new class a String instance variable called "requestId", which stores the unique ID of each fence.
      I also gave this new class a Marker and Circle instance variables to keep track of the fence's circle and marker that I put on my map, but you may not need to do this.

      Then, create a method that will remove a single fence. This method should take a String requestId as a parameter. Iterate through all your geofences using a for-loop and check their requestId until you find one that matches the requestId passed to the method. Save the index of the fence you want to remove.

      Now call .get() on your list of geofences and pass it the index of the fence you want to remove. Save the fence returned by get() in a variable:
      MyGeofence fenceToRemove = myGeofences.get(fenceToRemoveIndex);

      Then remove it from the list of fences:
      myGeofences.remove(fenceToRemoveIndex);

      Then remove its circle and marker from the map, this may be optional for you:
      fenceToRemove.getCircle().remove();
      fenceToRemove.getMarker().remove();

      If this fence has a pending intent, you must also remove that:
      String geoFenceRequestIdToRemove = fenceToRemove.getRequestId();
      //Remove from mGeofencePendingIntent
      List geoFenceToRemove = new ArrayList<>();
      geoFenceToRemove.add(geoFenceRequestIdToRemove);
      if (mGeofencePendingIntent != null) { LocationServices.GeofencingApi.removeGeofences(mGoogleApiClient,
      geoFenceToRemove).setResultCallback(this);
      } else {
      Log.d(TAG, "removeSelectedGeofence - GeoFence pending intent is null.");
      }

      Hopefully this helps!

      Delete
  2. This is helpful post and important tips at this post, It's a very uncommon topics i helpful us at this post..!

    ReplyDelete

Note: Only a member of this blog may post a comment.