Geofencing API Tutorial for Android
In this geofence tutorial, you’ll learn how to use Android’s Geofencing API to build an app with custom geofences. By Fernando Sproviero.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Geofencing API Tutorial for Android
20 mins
- Getting Started
- Obtaining a Google Maps API Key
- Building and Running the Starter Project
- Creating a Geofence
- Building the Geofence
- Building the Geofence Request
- Building the Geofence Pending Intent
- Checking for Errors
- Handling Transitions
- Creating the Transitions Intent Service
- Testing the Geofences
- Mocking the Location
- Creating a Reminder and Getting the Notification
- Removing a Geofence
- Best Practices
- Responsiveness
- Radius
- Dwell
- Re-registering Your Geofence
- Wi-Fi and Location Accuracy
- Where to Go From Here?
Geofences are a powerful tool in a developer’s arsenal of location tricks to use on Android. Geofences give devices the power to monitor a circular area in the world, and let the device inform you whenever it enters or exits that area.
This has enormous benefits for apps that want to leverage location as a trigger. A retail company could send a special notification to a customer near one of their stores with a special discount to tempt them in. A holiday resort could welcome its customers via its app whenever they enter the resort. With a limit of 100 geofences per device, the possibilities are nearly endless!
In this tutorial on geofencing, you’ll learn how to use Android’s geofencing API to build custom geofences in your very own app called Remind Me There. Let’s get to it!
Getting Started
The project you’ll work with, Remind Me There, is an app to create reminders based on geofences. You’ll set up custom geofences and messages; then, as you travel into a geofenced area, you’ll receive your custom message as a notification on your device.
Use the Download materials button at the top or bottom of this tutorial to download the starter project.
Once downloaded, open the starter project in Android Studio 3.2 or later.
Obtaining a Google Maps API Key
Because this app uses Google Maps, you’ll need to obtain an API key.
- Open Google Cloud Platform and create a new project.
Feel free to leave the Project Name as is. You won’t need the name going forward. Leave the default value for Location. Select Create.
- Select APIs & Services ▸ Library from the navigation menu.
- Select Maps SDK for Android.
- Click Enable, or Manage if already enabled.
- Click on Credentials, Credentials in the API Manager, Create credentials and then choose API key.
- Copy your API key value. In your project, open
debug/res/values/google_maps_api.xml
and replaceYOUR_KEY_HERE
with the copied value.
release/res/values/google_maps_api.xml
.
Building and Running the Starter Project
You can run the sample project on an Android device or emulator. For the emulator, you’ll need to make sure you have an emulator setup with Google APIs in order to show map information.
When you build and run the app, you’ll see the following screens:
![]() |
![]() |
![]() |
![]() |
Currently, tapping on the + button will let you create the reminder. However, the actual geofence is not yet being created. You’ll write code to create the geofence.
Review the project to get familiar with the files:
- MainActivity.kt: Shows the reminders in a map.
- NewReminderActivity.kt: This activity contains the code to create a new reminder, providing latitude/longitude, radius and a message to be shown in the notification.
- Reminder.kt: A model class used to store the reminder. It has an id, a latitude/longitude, a radius and a message. You’ll use this later to build a geofence.
- ReminderRepository.kt: This file saves the reminders that you create that will be shown in the MainActivity. You’ll also add code to create the geofences, here.
- BaseActivity.kt: This is the parent activity of MainActivity and NewReminderActivity. It provides common access to the ReminderRepository.
- ReminderApp.kt: When the app is launched, it creates the ReminderRepository.
- Utils.kt: Here, you’ll find a few generic functions useful for the app – for example, a function to hide the keyboard, show a notification, etc.
Using geofences requires the play-services-location library added to your project. To do that, open the build.gradle file for the app module and add the following dependency:
implementation 'com.google.android.gms:play-services-location:16.0.0'
Your app also requires the device location to know when to trigger a reminder. You do this by requesting the ACCESS_FINE_LOCATION permission.
This permission is set up in AndroidManifest.xml. It’s classified as a dangerous permission — that is, a permission that could potentially affect the user’s privacy. For Android 6 and later, it’s necessary to check and request this permission during runtime.
Because the starter project shows a map and the user location, the permission and runtime check were both already set up for you in AndroidMainfest.xml and MainActivity.kt.
Creating a Geofence
To manipulate geofences, you need to use the GeofencingClient, so open ReminderRepository.kt and add a new property:
private val geofencingClient = LocationServices.getGeofencingClient(context)
You’ll also need to import com.google.android.gms.location.LocationServices
.
If you look at the add()
method, you’ll see that, currently, it just saves the reminder to SharedPreferences. First, you’ll change this behavior to create the geofence, using the GeofencingClient. Then, only if the geofence creation is successful, you’ll save the reminder to SharedPreferences.
Change the add()
method to the following:
fun add(reminder: Reminder,
success: () -> Unit,
failure: (error: String) -> Unit) {
// 1
val geofence = buildGeofence(reminder)
if (geofence != null
&& ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
// 2
geofencingClient
.addGeofences(buildGeofencingRequest(geofence), geofencePendingIntent)
// 3
.addOnSuccessListener {
saveAll(getAll() + reminder)
success()
}
// 4
.addOnFailureListener {
failure("Error")
}
}
}
Use the hotkeys you learned above to import ContextCompat
and Manifest
. Don’t worry about the other errors. Those are methods that you’ll build, soon. Take a minute to have a look at what you have just added:
- You create the geofence model using the data from the reminder.
- You use the GeofencingClient to add the geofence that you’ve just built with a geofencing request and a pending intent. More on this later.
- If the geofence is added successfully, you save the reminder and call the
success
argument. - If there’s an error, you call the
failure
argument (without saving the reminder).
Building the Geofence
A geofence is defined by a latitude, a longitude and a radius. It’s a circular area at a specific location that an app can use to trigger particular behaviors when a device enters, exits or stays for a certain amount of time within geofence boundaries.
You’re going to build a geofence that behaves this way right now. To build your geofence, add the following method to ReminderRepository.kt:
private fun buildGeofence(reminder: Reminder): Geofence? {
val latitude = reminder.latLng?.latitude
val longitude = reminder.latLng?.longitude
val radius = reminder.radius
if (latitude != null && longitude != null && radius != null) {
return Geofence.Builder()
// 1
.setRequestId(reminder.id)
// 2
.setCircularRegion(
latitude,
longitude,
radius.toFloat()
)
// 3
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
// 4
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.build()
}
return null
}
Take a look at the Geofence.Builder
code in your new method:
- RequestId: This id uniquely identifies the geofence within your app. You obtain this from the reminder model.
- latitude, longitude and radius: You also get these from the reminder model at the top of the new method.
-
TransitionType: To trigger an event when the user enters the geofence, use
GEOFENCE_TRANSITION_ENTER
. Other options areGEOFENCE_TRANSITION_EXIT
andGEOFENCE_TRANSITION_DWELL
. You can learn more about transition options here. -
ExpirationDuration: Use
NEVER_EXPIRE
so this geofence will exist until the user removes it. The other option is to enter a duration (ms) after which the geofence will expire.
Building the Geofence Request
Add this method in ReminderRepository.kt to build the request:
private fun buildGeofencingRequest(geofence: Geofence): GeofencingRequest {
return GeofencingRequest.Builder()
.setInitialTrigger(0)
.addGeofences(listOf(geofence))
.build()
}
You use setInitialTrigger()
to set the desired behavior at the moment the geofences are added. Setting the value to 0 indicates that you don’t want to trigger a GEOFENCE_TRANSITION_ENTER
event if the device is already inside the geofence that you’ve just added.
You also use addGeofences()
to add your geofence to the request.
Building the Geofence Pending Intent
Next, add this lazy property:
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(context, GeofenceBroadcastReceiver::class.java)
PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT)
}
Here you’re creating a PendingIntent. A PendingIntent is similar to a normal Intent, except rather than happening immediately it will happen sometime in the future. Think of it as a promise to a friend that you’ll do something for them later.
In this case, when the GEOFENCE_TRANSITION_ENTER
event is triggered, it’ll launch GeofenceTransitionsJobIntentService to handle the event. This service will be explained later.
Checking for Errors
There might be times where it’s not possible to add a geofence — when you are trying to add the 101st geofence, for example. Review the error strings below to see some of the more common issues that can arise when working with geofences. It is good practice to check for these errors and let the user know (or at least log the error).
Open strings.xml and add the following strings:
<string name="geofence_unknown_error">
Unknown error: the Geofence service is not available now.
</string>
<string name="geofence_not_available">
Geofence service is not available now. Go to Settings>Location>Mode and choose High accuracy.
</string>
<string name="geofence_too_many_geofences">
Your app has registered too many geofences.
</string>
<string name="geofence_too_many_pending_intents">
You have provided too many PendingIntents to the addGeofences() call.
</string>
Now, create a file called GeofenceErrorMessages.kt with the following content:
package com.android.raywenderlich.remindmethere
import android.content.Context
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.location.GeofenceStatusCodes
object GeofenceErrorMessages {
fun getErrorString(context: Context, e: Exception): String {
return if (e is ApiException) {
getErrorString(context, e.statusCode)
} else {
context.resources.getString(R.string.geofence_unknown_error)
}
}
fun getErrorString(context: Context, errorCode: Int): String {
val resources = context.resources
return when (errorCode) {
GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE ->
resources.getString(R.string.geofence_not_available)
GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES ->
resources.getString(R.string.geofence_too_many_geofences)
GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS ->
resources.getString(R.string.geofence_too_many_pending_intents)
else -> resources.getString(R.string.geofence_unknown_error)
}
}
}
Here, you’ve added a Kotlin object with a method that takes in an error code and returns a more user friendly string representation of the error.
Open ReminderRepository.kt again and change the add()
method’s failure listener to this:
// 4
.addOnFailureListener {
failure(GeofenceErrorMessages.getErrorString(context, it))
}
Now, you are returning the error strings you created earlier.
Handling Transitions
Previously, you created a PendingIntent and set a class to handle the GEOFENCE_TRANSITION_ENTER
event. Remember that promise you made earlier? Now you’re going to follow up on that promise by writing a class to handle it.
Creating the Transitions Intent Service
Create the file GeofenceTransitionsJobIntentService.kt and add the following code:
package com.android.raywenderlich.remindmethere
import android.content.Context
import android.content.Intent
import android.support.v4.app.JobIntentService
import android.util.Log
import com.google.android.gms.location.Geofence
import com.google.android.gms.location.GeofencingEvent
class GeofenceTransitionsJobIntentService : JobIntentService() {
override fun onHandleWork(intent: Intent) {
// 1
val geofencingEvent = GeofencingEvent.fromIntent(intent)
// 2
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceErrorMessages.getErrorString(this,
geofencingEvent.errorCode)
Log.e(LOG_TAG, errorMessage)
return
}
// 3
handleEvent(geofencingEvent)
}
}
- You obtain the
GeofencingEvent
object using the intent. - If there’s an error, you log it.
- Otherwise, you handle the event.
Add the handleEvent()
method to your new class:
private fun handleEvent(event: GeofencingEvent) {
// 1
if (event.geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
// 2
val reminder = getFirstReminder(event.triggeringGeofences)
val message = reminder?.message
val latLng = reminder?.latLng
if (message != null && latLng != null) {
// 3
sendNotification(this, message, latLng)
}
}
}
- You first check if the transition is related to entering a geofence.
- If the user creates overlapping geofences, there may be multiple triggering events, so, here, you pick only the first reminder object.
- You then show the notification using the message from the reminder.
Now add the getFirstReminder()
method code:
private fun getFirstReminder(triggeringGeofences: List<Geofence>): Reminder? {
val firstGeofence = triggeringGeofences[0]
return (application as ReminderApp).getRepository().get(firstGeofence.requestId)
}
Here, you get the first triggered geofence and use its requestId to find the associated reminder object from the repository.
Add the following companion object:
companion object {
private const val LOG_TAG = "GeoTrIntentService"
private const val JOB_ID = 573
fun enqueueWork(context: Context, intent: Intent) {
enqueueWork(
context,
GeofenceTransitionsJobIntentService::class.java, JOB_ID,
intent)
}
}
Create the file GeofenceBroadcastReceiver.kt and add the following code:
package com.android.raywenderlich.remindmethere
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
class GeofenceBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
GeofenceTransitionsJobIntentService.enqueueWork(context, intent)
}
}
Finally, the receiver and service must be declared in the AndroidManifest.xml, so add them just before the application
closing tag:
<receiver
android:name=".GeofenceBroadcastReceiver"
android:enabled="true"
android:exported="true" />
<service
android:name=".GeofenceTransitionsJobIntentService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />
Testing the Geofences
I bet you are anxious to test what you’ve coded so far!
But, before that, it’s important to mention that, during development, it’s rather difficult to test this. Just imagine creating a few geofences separated by hundreds of feet or miles. You would need to walk or drive a lot! To address this, you can mock your location.
Mocking the Location
For quick and easy testing, you can use an Android emulator and modify its location coordinates. With an emulator running, open the extended controls by clicking the … button at the bottom of the menu:
Modify the coordinates to something else, for example, a latitude and longitude near your current actual location, and press SEND:
After a few seconds, you’ll see the user location updated in the map:
![]() |
![]() |
Creating a Reminder and Getting the Notification
Now that you know how to easily mock your location:
- Build and run the app.
- Create a reminder on your current location.
- Modify your coordinates so that your current location is outside of the geofence.
- Now, modify your coordinates so that your current location is inside the geofence.
- Wait a few seconds and you’ll get a notification with the message you set.
Congratulations, you’ve added your first geofence reminder!
Removing a Geofence
If you tap on a reminder, you’ll see that you get the option to remove it. Currently, this action just removes the reminder from SharedPreferences. Now, you’ll change the remove()
method so it also removes the geofence.
Open ReminderRepository.kt and modify the remove()
method:
fun remove(reminder: Reminder,
success: () -> Unit,
failure: (error: String) -> Unit) {
geofencingClient
.removeGeofences(listOf(reminder.id))
.addOnSuccessListener {
saveAll(getAll() - reminder)
success()
}
.addOnFailureListener {
failure(GeofenceErrorMessages.getErrorString(context, it))
}
}
If you compare this with the add()
method you created earlier, you’ll notice that they’re very similar. Here, you’re just removing the geofence and reminder instead of adding them — easy!
Best Practices
When working in a mobile environment, it is always important to consider issues such as battery drain, bandwidth and connectivity. Here are a few things to keep in mind as you develop your awesome geofence-based apps:
Responsiveness
To save battery, you can use the setNotificationResponsiveness()
method to decrease how frequently the app checks for an event. For example, if you set it to 180,000 milliseconds, the callback will be called every three minutes to see if a device has entered/exited a geofence. The default is zero.
Radius
Android recommends a minimum geofence radius of 100–150m. When deciding on a radius, think about the use cases for your app. Will it primarily be used while walking indoors? Driving in rural areas? Wi-Fi availability makes a big difference for device location accuracy; if there is poor or no Wi-Fi in a specific region, try using a larger radius.
Dwell
Another way to reduce your app’s power usage is to use GEOFENCE_TRANSITION_DWELL
and setLoiteringDelay()
when building the geofence. For example, setLoiteringDelay(360000)
means that the user will have to stay within the geofence for six minutes to trigger an event. Doing this could also improve the user experience by reducing unintended notifications if a device’s location reading is not stable or if the user is traveling along the edge of a geofence.
Re-registering Your Geofence
Only re-register your geofences under the following circumstances:
- Device reboot: You can register a BroadcastReceiver for
BOOT_COMPLETED
. - App is uninstalled and re-installed.
- App data is cleared.
- Google Play services data is cleared.
-
GEOFENCE_NOT_AVAILABLE error
: You may get this error in theGeofenceTransitionsJobIntentService
. Usually this is because the Network Location Provider is disabled.
Wi-Fi and Location Accuracy
- Latency for triggering a geofence transition is usually two minutes on average. However, if the device has been stationary, the latency may increase to up to six minutes.
- On most devices, the geofence service uses the Network Location Provider. This provider determines location based on cell towers and Wi-Fi access points. It works indoors and uses much less power than GPS.
- If there’s no reliable connection to cell towers or Wi-Fi access points, geofence transitions might not trigger.
- If Wi-Fi is disabled, the geofence service may not trigger the transitions. For Android 4.3 and later, there’s a “Wi-Fi scan only mode” that disables Wi-Fi but not the network location.
Where to Go From Here?
Congratulations! You’ve just learned the basics of geofences on Android.
You can download the final version of the project using the Download materials button at the top or bottom of this tutorial. Remember to add your Google Maps API key to the final project before you run it.
Here are some great references to learn more about the subject:
- Official Docs: You can find them here.
- Fence API: To be aware also of the context (walking, driving, headphones plugged in, etc.), check out more here.
- The background processing guide will help you choose the best way for your app to perform operations in the background.
- There’s a Google sample that you might want to check out.
Feel free to share your feedback, findings or ask any questions in the comments below or in the forums. I hope you enjoyed this tutorial on geofences!