Android GeofencingAPI

Intro

The Android GeofencingApi makes it easy to define a circular virtual fence around a point on a map and monitor for transitions as the device crosses the virtual fence line.

I recently built an app that used this api to send alerts as the device approaches a location. This was accomplished by defining several concentric geofences around the destination point and responding to the GEOFENCE_TRANSITION_ENTER trigger as the device crossed each geofence as it moved towards the destination.

This walkthrough will show how the api was used to create and monitor geofences, and talk about some lessons learned.

Setup

To make use of the Android GeofencingAPI, we need an instance of GoogleApiClient from the Google Play Services API. To instantiate the client, our code must have access to the library containing the api classes. This is accomplished by expressing a dependency in the app build.gradle file:

dependencies {
    …

    compile 'com.google.android.gms:play-services-location:10.2.1'

}

We also need to get the user’s permission to have access to the device’s location. This is accomplished by declaring the use of the permission in your AndroidManifest.xml file:


<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Code

To start using the GeofencingAPI in our app, we create a GoogleApiClient field in the MainActivity class, and make sure the class implements the GoogleApiClient.ConnectionCallbacks and GoogleApiClient.OnConnectionFailedListener
interfaces.

public class MainActivity extends AppCompatActivity

        implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {

    private GoogleApiClient googleApiClient;

We instantiate the GoogleApiClient in the onCreate method.

   @Override

   protected void onCreate(Bundle savedInstanceState) {
    
      super.onCreate(savedInstanceState);
    
      setContentView(R.layout.activity_main);

    
      if (googleApiClient == null) {
        
         googleApiClient = new GoogleApiClient.Builder(this)
                
                             .addConnectionCallbacks(this)
                
                             .addOnConnectionFailedListener(this)
                
                             .addApi(LocationServices.API)
                
                             .build();
    
      }


   }

This tells the client that we want to use LocationServices.API and what class will be listening for connection callbacks and failed connections.

Before we can call methods on the client, we need to connect the client to Google Play services – this is done in the onStart() lifecycle method. It is good practice to disconnect the client after we’re done using it – this is done in the onStop() lifecycle method.

   @Override

   protected void onStart() {

       super.onStart();

       googleApiClient.connect();
   
}



   @Override

   protected void onStop() {

       googleApiClient.disconnect();

       super.onStop();
   
}


Now we can use the client to call the GeofencingAPI. First we get our current location so we can calculate our proximity to the destination. We’ll need this to decide how many geofences to create.

   Location currentLocation = LocationServices.FusedLocationApi.getLastLocation(googleApiClient);


Next we define a destination.


   // create a destination

   Location destination = new Location("GeofenceAPI demo");

   destination.setLatitude(41.246102);

   destination.setLongitude(-96.022887);


We need to know how far away the destination is.

   float distanceInMeters = currentLocation.distanceTo(destination);


Now we can build a list of concentric geofences – one at 100 meters from the destination, one at 500 meters and one a 1 km. Notice we set the ids of the geofences to something we could display back to the user.

   List geofenceList = new ArrayList();


   // create concentric geofences

   if (distanceInMeters > 100f) {

       geofenceList.add(

          new Geofence.Builder()

                .setRequestId("Arrived at Destination!")

                .setCircularRegion(

                   destination.getLatitude(),

                   destination.getLongitude(),

                   100f)

                .setExpirationDuration(60*60*1000)
            
                .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
            
                .setNotificationResponsiveness(0)

                .build());

       if (distanceInMeters > 500f) {

           geofenceList.add(

              new Geofence.Builder()

                    .setRequestId("500 meters to destination")

                    .setCircularRegion(

                       destination.getLatitude(),
                        
                       destination.getLongitude(),

                       500f)

                    .setExpirationDuration(60*60*1000)
                
                    .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
                
                    .setNotificationResponsiveness(0)

                    .build());

          }

       if (distanceInMeters > 1000f) {

           geofenceList.add(

              new Geofence.Builder()

                    .setRequestId("1km to destination")

                    .setCircularRegion(
                                
                       destination.getLatitude(),
                                
                       destination.getLongitude(),

                       1000f)

                    .setExpirationDuration(60*60*1000)
                        
                    .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
                        
                    .setNotificationResponsiveness(0)

                    .build()
);

          }


The GeofencingAPI will fire an Intent when the specified transition trigger occurs. We create a PendingIntent that holds the Intent we want to be fired.

    
// build a pending intent
 
   Intent intent = new Intent(this, GeofenceDemoIntentService.class);

    PendingIntent pendingIntent = PendingIntent.getService(this, 123, intent, PendingIntent.FLAG_UPDATE_CURRENT);


Now we build a GeofencingRequest, passing it the list of geofences.


    // build geofencing request

    GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
    
    builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
    
    builder.addGeofences(geofenceList);

    GeofencingRequest geofencingRequest = builder.build();


Finally, we ask the GeofencingAPI to monitor our location for geofence transitions.


    LocationServices.GeofencingApi.addGeofences(

            googleApiClient,

            geofencingRequest,

            pendingIntent
)
    .setResultCallback(this);



As our device moves towards the destination, the GeofencingAPI detects that we have crossed a geofence boundary and fires the intent we specified. That intent is received by the onHandleIntent method of our GeofenceDemoIntentService class.

public class GeofenceDemoIntentService extends IntentService {



    public GeofenceDemoIntentService() {

        super("GeofenceDemoIntentService");

    }



    @Override

    protected void onHandleIntent(@Nullable Intent intent) {

        GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);

        if (geofencingEvent.hasError()) {

            return;

        }

        int geofenceTransitionType = geofencingEvent.getGeofenceTransition();

        if (geofenceTransitionType == Geofence.GEOFENCE_TRANSITION_ENTER) {

            List triggeringGeofences = geofencingEvent.getTriggeringGeofences();


            // inspect triggeringGeofences and do something interesting


        }


    }

}

Once we have the triggering geofences from the GeofencingEvent that fired our Intent, we can take whatever action is desired – send an alert, launch another activity, etc.

Lessons Learned

Detection Latency

There is occasional latency in the GeofencingAPI’s detection of transitions, and the latency is unpredictable. It is not unusual to have multiple fences returned by geofencingEvent.getTriggeringGeofences(). My testing was done with my trusty but aging Nexus 5, so perhaps with a newer device, the latency would be less of an issue.

Too Many Fences

Early on, I was creating far too many geofences. Considering the latency mentioned above, my use case was better served by reducing the number of geofences.

Radius Too Small

If the radius of a geofence is small, it may be too small to be dependably detected. If the radius is small enough, and the device is moving fast enough, the transition may not be detected at all.

Remove Unneeded Fences

It is good practice to remove geofences when they are no longer needed. Be sure to pass the same PendingIntent that was used to create the fences.

if (googleApiClient.isConnected()) {

    LocationServices.GeofencingApi.removeGeofences(

            googleApiClient,

            pendingIntent

    ).setResultCallback(this);

}

Wrap Up

The Android GeofencingAPI is easy to use. By using the api to create concentric geofences around a single destination point, we were able to be alerted as our device approached a destination. Hopefully this example will help you use the GeofencingAPI for other interesting geolocation use cases.

Read more at Google’s Android API training documentation.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*