Geofences on Android with GoogleApiClient

In this tutorial you’ll learn how to leverage GoogleApiClient to add geofences to an Android app, as well as post notifications when a geofence is crossed. By Joe Howard.

Leave a rating/review
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Displaying a Notification

There’s nothing yet to inform a user when they’ve reached one of their geofences. Time to fix that!

Open up AreWeThereIntentService.java; you’ll see the starter project includes an empty onHandleIntent() implementation. The pending intent you associated with the geofence triggers AreWeThereIntentService and fires the onHandleIntent() callback.

Add the following implementation to onHandleIntent(), importing the missing headers as you go:

prefs = getApplicationContext().getSharedPreferences(
    Constants.SharedPrefs.Geofences, Context.MODE_PRIVATE);
gson = new Gson();

// 1. Get the event
GeofencingEvent event = GeofencingEvent.fromIntent(intent);
if (event != null) {
  if (event.hasError()) {
    onError(event.getErrorCode());
  } else {

    // 2. Get the transition type
    int transition = event.getGeofenceTransition();
    if (transition == Geofence.GEOFENCE_TRANSITION_ENTER ||
      transition == Geofence.GEOFENCE_TRANSITION_DWELL ||
        transition == Geofence.GEOFENCE_TRANSITION_EXIT) {
      List<String> geofenceIds = new ArrayList<>();

      // 3. Accumulate a list of event geofences
      for (Geofence geofence : event.getTriggeringGeofences()) {
        geofenceIds.add(geofence.getRequestId());
      }
      if (transition == Geofence.GEOFENCE_TRANSITION_ENTER ||
        transition == Geofence.GEOFENCE_TRANSITION_DWELL) {
        // 4. Pass the geofence list to the notification method
        onEnteredGeofences(geofenceIds);
      }
    }
  }

}

Once you set the values of the prefs and gson properties of the service, you do the following:

  1. Create a GeofencingEvent object from the service intent.
  2. Create an integer that stores the type of geofence transition.
  3. Build up a list of ids that triggered the service.
  4. Pass that list on to the notification method.

Add the following code to the beginning of onEnteredGeofences(), importing any missing headers:

// 1. Outer loop over all geofenceIds
for (String geofenceId : geofenceIds) {
  String geofenceName = "";

  // 2, Loop over all geofence keys in prefs and retrieve NamedGeofence from SharedPreferences
  Map<String, ?> keys = prefs.getAll();
  for (Map.Entry<String, ?> entry : keys.entrySet()) {
    String jsonString = prefs.getString(entry.getKey(), null);
    NamedGeofence namedGeofence = gson.fromJson(jsonString, NamedGeofence.class);
    if (namedGeofence.id.equals(geofenceId)) {
      geofenceName = namedGeofence.name;
      break;
    }
  }

  // 3. Set the notification text and send the notification
  String contextText =
          String.format(this.getResources().getString(R.string.Notification_Text), geofenceName);
}

Here’s what happens when the device enters a geofence:

  1. Loop over all the geofence ids.
  2. Then loop again to pull out the geofence name from SharedPreferences.
  3. Finally, you create a string to display to the user.

Now add the following to the end of the outer for loop, just below the creation of contextText:

// 1. Create a NotificationManager
NotificationManager notificationManager =
    (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);

// 2. Create a PendingIntent for AllGeofencesActivity
Intent intent = new Intent(this, AllGeofencesActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingNotificationIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

// 3. Create and send a notification
Notification notification = new NotificationCompat.Builder(this)
  .setSmallIcon(R.mipmap.ic_launcher)
  .setContentTitle(this.getResources().getString(R.string.Notification_Title))
  .setContentText(contextText)
  .setContentIntent(pendingNotificationIntent)
  .setStyle(new NotificationCompat.BigTextStyle().bigText(contextText))
  .setPriority(NotificationCompat.PRIORITY_HIGH)
  .setAutoCancel(true)
  .build();
notificationManager.notify(0, notification);

It looks dense, but it’s fairly straightforward code:

  1. You create a notification manager using getSystemService().
  2. Next, you create an intent to start up AllGeofencesActivity.
  3. The builder pattern creates the notification with the intent using the app icon, a string constant title, and the context string.

Build and run. Nothing has changed visually, but all the plumbing is in place to show a notification to the user when they enter a geofence.

Time to take your app on a virtual road trip to test this all out! :]

Testing with Mock Locations

Ideally, you’d test this on a device and physically move into the geofences you created.

If you’re working with the emulator, or you don’t quite have the resources to fly to Bora Bora right now, there’s a way to mock your location and trigger your geofences in a virtual fashion. This tutorial focuses on mocking through the emulator, but you can also mock locations on a device using third-party apps such a Fake GPS and My Fake Location.

AWTY-wemock

On the emulator, go to Settings\Developer options. Make sure Allow mock locations is already on:

AWTY-AllowMocks

Build and run the app in the emulator, and add some geofences.

In order to mock your location, you can use the Android Debug Bridge or telnet from the command line, but in this case you’ll use the Android DDMS which is bundled with Android Studio.

From the menu, choose Tools\Android\Android Device Monitor to open the DDMS window.

Choose the Emulator Control tab and locate (sorry!) Location Controls near the bottom:

AWTY-DDMS

Enter the longitude and latitude of one of your geofences for your mock location; note that the fields are reversed from your app’s interface. Click Send to mock the emulator location. You will likely need to open the Google Maps app on the emulator to trigger a location lookup.

If you don’t see anything pop up, click and drag the emulator screen down from where the battery is shown to display the notification center. At that point, you should see the geofence notification in the emulator:

AWTY-GC-Notification

Now you can say, with all certainty, “YES, we’re here!” :]

Where To Go From Here?

You can download the final project for this tutorial here.

You’ve come a long way in this tutorial: you’ve learned how to add and remove geofences, and trigger notifications for your user.

Geofences can be finicky on device reboot, as well as when Location services turn on and off. As a nice challenge for yourself, create a broadcast receiver that listens for BOOT_COMPLETED and reregisters your app’s stored geofences on reboot.

Also be sure to check out the Google Play Services overview on developer.android.com to find out what else you can do with Google Play Services and GoogleApiClient.

If you have any questions or comments on this tutorial, feel free to join the forum discussion below!

Contributors

Over 300 content creators. Join our team.