Geofencing
The Mapbox Maps SDK for Android's geofencing service enables your application to observe when a device enters, exits, or remains within your own defined geofences (a virtual fence around a geographic feature).
You can use these monitored events to trigger actions, alerts, and notifications.
See how the GeofencingService
is used to execute actions when a device enters, exits, or remains within the geofences.
Before you can use the geofencing component, users must grant your application permission to access their location. See sections Step 2: (Optional) Configure permissions and Permission handling for more details. Moreover, the user must grant background location permission if you need to observe events while the app is in the background.
Geofencing service
The GeofencingService is the main entry point to the Mapbox geofencing component. It allows you to manage geofences, start/stop observing events and configure the geofencing service.
After user has granted location permission you can get a GeofencingService
instance and configure it:
if (PermissionsManager.areLocationPermissionsGranted(context)
&& PermissionsManager.isBackgroundLocationPermissionGranted(context)) {
// Permissions are granted, we can use GeofencingService
val geofencing: GeofencingService = GeofencingFactory.getOrCreate()
// Optionally, configure geofencing service. For example, increase maximum number of geofences:
val geofencingOptions = geofencingOptions { maximumMonitoredFeatures = 300_000 }
geofencing.configure(geofencingOptions) { result ->
result.onError { geofenceError ->
Log.d(TAG, "geofence.configure() error $geofenceError")
}
}
}
Check GeofencingOptions.Builder for all the available configuration options.
Once the geofencing service is created and configured, you can add observers for geofencing events.
Observing events
Use the GeofencingObserver to be notified when user's device enters, exits, or remains within geofences.
// Build your GeofencingObserver
private val observer: GeofencingObserver = object : GeofencingObserver {
override fun onEntry(event: GeofencingEvent) {
// Inside the event you can find the geofence feature triggered
val feature: Feature = event.feature
Log.d(TAG, "onEntry() called with: feature id = ${feature.id()} at ${event.timestamp}")
}
override fun onDwell(event: GeofencingEvent) {
Log.d(TAG, "onDwell() called with: feature id = ${event.feature.id()} at ${event.timestamp}")
}
override fun onExit(event: GeofencingEvent) {
Log.d(TAG, "onExit() called with: feature id = ${event.feature.id()} at ${event.timestamp}")
}
override fun onError(error: GeofencingError) {
Log.d(TAG, "onError() called with: error = $error")
}
override fun onUserConsentChanged(isConsentGiven: Boolean) {
Log.d(TAG, "onUserConsentChanged() called with: isConsentGiven = $isConsentGiven")
}
}
// Then, add it to the geofencing service
geofencing.addObserver(observer) { result: Expected<GeofencingError, Unit> ->
if (result.isError) {
logD(TAG, "addObserver() failed: ${result.error}")
} else {
logD(TAG, "Geofencing observer added successfully")
}
}
If your app uses geofencing while in the background, we recommend adding the observer as part of the Application.onCreate
.
Geofences management
You must provide a GeoJSON feature for each geofence you want to create. The following geoJSON geometries are supported:
- Point: the monitored area is a circle around the point defined by the radius given in the
Feature.properties
(see GeofencingPropertiesKeys). - Polygon: the area is described by the polygon.
- MultiPolygon: events will be triggered whenever user's device enters/exits any of the multiple polygons defined in the geometry.
You can visit geojson.io to create and edit GeoJSON features.
Each feature must define a unique string identifier (id
). For example:
{
"type": "Feature",
"id": "eiffel_tower",
"properties": {
"name": "Eiffel Tower"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[2.291903984967945, 48.85772344046961],
[2.293390631169075, 48.85669301371263],
[2.296888622230682, 48.859015366890844],
[2.2952747763545176, 48.860087588194006],
[2.291903984967945, 48.85772344046961]
]
]
}
}
To add above GeoJSON feature to the geofencing service you must call the addFeature method:
val feature:Feature = Feature.fromJson(
"""
{
"type": "Feature",
"id": "eiffel_tower",
"properties": {
"name": "Eiffel Tower"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[ 2.291903984967945, 48.85772344046961 ],
[ 2.293390631169075, 48.85669301371263 ],
[ 2.296888622230682, 48.859015366890844 ],
[ 2.2952747763545176, 48.860087588194006 ],
[ 2.291903984967945, 48.85772344046961 ]
]
]
}
}
""".trimIndent())
geofencing.addFeature(feature) { result ->
if (result.isError) {
Log.e(TAG, "Failed to add feature error: ${result.error}")
} else {
Log.d(TAG, "Feature [${result.value}] added successfully")
}
}
To update a geofence you need to call addFeature
again with the same feature identifier. For example, let's add a new property rating
:
val updatedFeature:Feature = Feature.fromJson(
"""
{
"type": "Feature",
"id": "eiffel_tower",
"properties": {
"name": "Eiffel Tower",
"rating": 4.5
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[ 2.291903984967945, 48.85772344046961 ],
[ 2.293390631169075, 48.85669301371263 ],
[ 2.296888622230682, 48.859015366890844 ],
[ 2.2952747763545176, 48.860087588194006 ],
[ 2.291903984967945, 48.85772344046961 ]
]
]
}
}
""".trimIndent())
geofencing.addFeature(updatedFeature) { result ->
if (result.isError) {
Log.e(TAG, "Failed to add feature error: ${result.error}")
} else {
Log.d(TAG, "Feature [${result.value}] added successfully")
}
}
Later on, you can remove the feature by calling removeFeature:
val id = "eiffel_tower"
geofencing.removeFeature(id) { result ->
if (result.isError){
Log.e(TAG, "Failed to remove feature error: ${result.error}")
} else {
Log.d(TAG, "Feature [$id] removed successfully")
}
}
You can also retrieve geofences directly from the service by their id
:
val id = "eiffel_tower"
geofencing.getFeature(id) { result ->
if (result.isError) {
Log.e(TAG, "Failed to get feature error: ${result.error}")
} else {
val eiffelTowerFeature = result.value!!.feature
Log.d(TAG, "Feature [$id] found: $eiffelTowerFeature")
}
}
You can call clearFeatures to remove all geofences:
geofencing.clearFeatures { result ->
if (result.isError) {
Log.e(TAG, "Failed to clear features error: ${result.error}")
} else {
Log.d(TAG, "All features removed successfully")
}
}
For the geofencing service to generate dwell events each geofence feature must define a dwell time in minutes in its Feature.properties
(see GeofencingPropertiesKeys):
{
"type": "Feature",
"id": "eiffel_tower",
"properties": {
...
"MBX_GEOFENCE_DWELL_TIME": 20
},
"geometry": { ... }
}