Skip to content

Geofence

classGeofence

A geofence definition for monitoring circular or polygon regions.

The SDK implements the native iOS and Android Geofencing APIs, extended with polygon support and a proximity-based infinite-geofencing system that overcomes the platform limits of 20 (iOS) and 100 (Android) simultaneous geofences.

Contents

Overview
Field Required Description
identifier Unique name for this geofence.
latitude ✅* Center latitude (*omit for polygon geofences).
longitude ✅* Center longitude (*omit for polygon geofences).
radius ✅* Radius in meters (*omit for polygon geofences).
notifyOnEntry Fire event on entry.
notifyOnExit Fire event on exit.
notifyOnDwell Fire event after loitering for loiteringDelay ms.
vertices Polygon geofence vertices (replaces lat/lng/radius).
extras Arbitrary key-value metadata posted with each event.

Warning

Both platforms require GeolocationConfig.locationAuthorizationRequest to be "Always". Geofencing does not work with "WhenInUse" only.


Adding geofences

Use BGGeo.addGeofence for a single geofence, or BGGeo.addGeofences for bulk inserts (approximately 10× faster than inserting individually).

If a geofence with the same identifier already exists in the database, it is replaced.


Listening for events

Subscribe to geofence transitions with BGGeo.onGeofence. Subscribe to changes in the actively monitored set with BGGeo.onGeofencesChange.

val bgGeo = BGGeo.instance

// Listen for geofence events.
bgGeo.onGeofence { geofence ->
    Log.d(TAG, "[geofence] ${geofence.identifier} ${geofence.action}")
}

Note

When all geofences are removed, BGGeo.onGeofencesChange fires with empty arrays for both on and off.


Polygon geofencing

The SDK supports polygon geofences of any shape via the vertices field. Polygon geofencing is sold as a separate add-on but is fully functional in DEBUG builds.

When defining a polygon geofence, omit latitude, longitude, and radius — the SDK calculates the minimum enclosing circle automatically from the polygon geometry.

val bgGeo = BGGeo.instance

bgGeo.onGeofencesChange { event ->
    val on = event.on   // <-- new geofences activated.
    val off = event.off // <-- geofences that were just de-activated.

    // Create map circles
    on.forEach { geofence ->
        Log.d(TAG, "activated: ${geofence.identifier}")
    }

    // Remove map circles
    off.forEach { identifier ->
        Log.d(TAG, "deactivated: $identifier")
    }
}

Infinite geofencing

The SDK stores all geofences in its database and uses a geospatial query to activate only the nearest geofences within GeolocationConfig.geofenceProximityRadius, staying within the platform limit. As the device moves, the active set is periodically refreshed.

  • The minimum GeolocationConfig.geofenceProximityRadius is enforced at 1000 meters.
  • Geofences within the radius (green in the diagram below) are actively monitored.
  • Geofences outside the radius (grey) remain in the database but are dormant.


Removing geofences

Geofences persist in the SDK's database until explicitly removed. If AppConfig.stopOnTerminate is false and AppConfig.startOnBoot is true, geofences continue to be monitored across app termination and device reboots.

// Within a coroutine scope
val bgGeo = BGGeo.instance

bgGeo.geofences.remove("HOME")
Log.d(TAG, "[removeGeofence] success")
// Within a coroutine scope
val bgGeo = BGGeo.instance

bgGeo.geofences.removeAll()
Log.d(TAG, "[removeGeofences] all geofences have been destroyed")
// Within a coroutine scope
val bgGeo = BGGeo.instance

val geofences = bgGeo.geofences.getAll()
Log.d(TAG, "[getGeofences] $geofences")

Geofences-only mode

Call BGGeo.startGeofences instead of BGGeo.start to monitor geofences without continuous location tracking. Use GeolocationConfig.geofenceModeHighAccuracy to improve event responsiveness at the cost of higher power usage.

The SDK can switch between full tracking and geofences-only mode at any time by calling the corresponding start method.

// Within a coroutine scope
val bgGeo = BGGeo.instance

bgGeo.onGeofence { geofence ->
    Log.d(TAG, "[geofence] $geofence")
}

bgGeo.ready {
    http.url = "http://your.server.com/geofences"
    http.autoSync = true
    geolocation.geofenceModeHighAccuracy = true // <-- consumes more power; default is false.
}

// engage geofences-only mode:
bgGeo.startGeofences()

Examples
// Within a coroutine scope
val bgGeo = BGGeo.instance

// Listen to geofence events
bgGeo.onGeofence { geofence ->
    Log.d(TAG, "[geofence] $geofence")
    if (geofence.identifier == "DANGER_ZONE") {
        if (geofence.action == "ENTER") {
            // Entering the danger-zone, we want to aggressively track location.
            kotlinx.coroutines.runBlocking { bgGeo.start() }
        } else if (geofence.action == "EXIT") {
            // Exiting the danger-zone, we resume geofences-only tracking.
            kotlinx.coroutines.runBlocking { bgGeo.startGeofences() }
        }
    }
}

// Add a geofence.
val geofence = Geofence.Builder()
    .setIdentifier("DANGER_ZONE")
    .setRadius(1000f)
    .setLatitude(45.51921926)
    .setLongitude(-73.61678581)
    .setNotifyOnEntry(true)
    .setNotifyOnExit(true)
    .build()

bgGeo.geofences.add(geofence)

// Ready the plugin.
bgGeo.ready {
    geolocation.desiredAccuracy = DesiredAccuracy.HIGH
    geolocation.distanceFilter = 10f
    http.url = "http://your.server.com/locations"
    http.autoSync = true
}

bgGeo.startGeofences()
Single geofence
// Within a coroutine scope
val bgGeo = BGGeo.instance

val geofence = Geofence.Builder()
    .setIdentifier("Home")
    .setRadius(200f)
    .setLatitude(45.51921926)
    .setLongitude(-73.61678581)
    .setNotifyOnEntry(true)
    .setNotifyOnExit(true)
    .setExtras(mapOf("route_id" to 1234))
    .build()

try {
    bgGeo.geofences.add(geofence)
    Log.d(TAG, "[addGeofence] success")
} catch (e: Exception) {
    Log.d(TAG, "[addGeofence] FAILURE: $e")
}
Multiple geofences
// Within a coroutine scope
val bgGeo = BGGeo.instance

val geofences = listOf(
    Geofence.Builder()
        .setIdentifier("Home")
        .setRadius(200f)
        .setLatitude(45.51921926)
        .setLongitude(-73.61678581)
        .setNotifyOnEntry(true)
        .build(),
    Geofence.Builder()
        .setIdentifier("Work")
        .setRadius(200f)
        .setLatitude(45.61921927)
        .setLongitude(-73.71678582)
        .setNotifyOnEntry(true)
        .build()
)

bgGeo.geofences.addAll(geofences)
Log.d(TAG, "[addGeofences] success")
Polygon geofence
// Within a coroutine scope
val bgGeo = BGGeo.instance

val geofence = Geofence.Builder()
    .setIdentifier("Park")
    .setNotifyOnEntry(true)
    .setNotifyOnExit(true)
    .setVertices(listOf(
        listOf(45.518947279987714, -73.6049889209514),
        listOf(45.5182711292279, -73.60338649600598),
        listOf(45.517082240237634, -73.60432670908212),
        listOf(45.51774871402813, -73.60604928622278)
    ))
    .build()

bgGeo.geofences.add(geofence)
Remove a single geofence
// Within a coroutine scope
val bgGeo = BGGeo.instance

bgGeo.geofences.remove("Home")
Remove all geofences
// Within a coroutine scope
val bgGeo = BGGeo.instance

bgGeo.geofences.removeAll()
Fetch all stored geofences
// Within a coroutine scope
val bgGeo = BGGeo.instance

val geofences = bgGeo.geofences.getAll()
Log.d(TAG, "[getGeofences] $geofences")
Geofences-only mode
// Within a coroutine scope
val bgGeo = BGGeo.instance

bgGeo.onGeofence { event ->
    Log.d(TAG, "[onGeofence] $event")
}

bgGeo.ready {
    http.url = "https://your.server.com/geofences"
    http.autoSync = true
    geolocation.geofenceModeHighAccuracy = true
}

bgGeo.startGeofences()
Toggle between location tracking and geofences-only mode
// Within a coroutine scope
val bgGeo = BGGeo.instance

bgGeo.onGeofence { event ->
    if (event.identifier == "DANGER_ZONE") {
        if (event.action == "ENTER") {
            // Entering the zone — switch to full location tracking.
            kotlinx.coroutines.runBlocking { bgGeo.start() }
        } else if (event.action == "EXIT") {
            // Exiting the zone — return to geofences-only mode.
            kotlinx.coroutines.runBlocking { bgGeo.startGeofences() }
        }
    }
}

val geofence = Geofence.Builder()
    .setIdentifier("DANGER_ZONE")
    .setRadius(1000f)
    .setLatitude(45.51921926)
    .setLongitude(-73.61678581)
    .setNotifyOnEntry(true)
    .setNotifyOnExit(true)
    .build()

bgGeo.geofences.add(geofence)

bgGeo.ready {
    geolocation.desiredAccuracy = DesiredAccuracy.HIGH
    geolocation.distanceFilter = 10f
    http.url = "https://your.server.com/locations"
    http.autoSync = true
}

bgGeo.startGeofences()

Members

entryState

val entryState: EntryState

Current entry state of the geofence.

Value State
0 Outside
1 Inside

extras

val extras: Map<String, Any>?

Arbitrary key-value metadata attached to each geofence event and included in the payload posted to HttpConfig.url.

hits

val hits: Int

Number of times this geofence has been triggered since it was added.

identifier

val identifier: String

Unique identifier for this geofence.

Used to reference the geofence in events and removal calls. Adding a geofence with an identifier that already exists replaces the existing one.

latitude

val latitude: Double

Latitude of the circular geofence center.

Omit when defining a polygon geofence via vertices — the SDK calculates the center automatically.

loiteringDelay

val loiteringDelay: Int

Minimum time in milliseconds the device must remain inside the geofence before a notifyOnDwell event fires. Default 0.

longitude

val longitude: Double

Longitude of the circular geofence center.

Omit when defining a polygon geofence via vertices — the SDK calculates the center automatically.

notifyOnDwell

val notifyOnDwell: Boolean

Fire a GeofenceEvent when the device has remained inside this geofence for loiteringDelay milliseconds.

notifyOnEntry

val notifyOnEntry: Boolean

Fire a GeofenceEvent when the device enters this geofence.

See also - GeolocationConfig.geofenceInitialTriggerEntry

notifyOnExit

val notifyOnExit: Boolean

Fire a GeofenceEvent when the device exits this geofence.

radius

val radius: Float

Radius of the circular geofence in meters.

Omit when defining a polygon geofence via vertices — the SDK calculates the enclosing radius automatically.

Warning

The minimum reliable radius is 200 meters. Below this threshold, geofences may not trigger reliably on either platform.

Apple documents this explicitly:

"For testing purposes, you can assume that the minimum distance is approximately 200 meters."

stateUpdatedAt

val stateUpdatedAt: Date?

Epoch timestamp in seconds of the last geofence transition.

vertices

val vertices: List<List<Double>>?

Polygon geofence vertices as [[lat, lng], ...] pairs.

When provided, the geofence is treated as a polygon rather than a circle. Omit latitude, longitude, and radius — the SDK solves the minimum enclosing circle from the vertex geometry and registers that as the native circular geofence.

When the device enters the enclosing circle, the SDK begins C++ hit-testing against the polygon at high frequency. When it exits the circle, polygon monitoring ceases.

Note

Polygon geofencing is sold as a separate add-on but is fully functional in DEBUG builds.

// Within a coroutine scope
val bgGeo = BGGeo.instance

val geofence = Geofence.Builder()
    .setIdentifier("Home")
    .setNotifyOnEntry(true)
    .setNotifyOnExit(true)
    .setVertices(listOf(
        listOf(45.518947279987714, -73.6049889209514),
        listOf(45.5182711292279, -73.60338649600598),
        listOf(45.517082240237634, -73.60432670908212),
        listOf(45.51774871402813, -73.60604928622278)
    ))
    .build()

bgGeo.geofences.add(geofence)