Location and geofences
How Bubbl watches your campaign locations, what permission it needs for geofences to work with the app closed, and what each platform allows.
Your team draws locations and builds geofence campaigns in the dashboard. The SDK keeps the nearest geofences watched by the OS and reports each time the device enters or leaves one. The server then decides whether a notification follows: cooldowns, trigger limits and quiet hours are set in the dashboard, not in the app.
What Bubbl needs#
- "Always" location ("Allow all the time" on Android) for geofences to work with the app closed or swiped away. The OS then wakes the app for each event.
- With location only while in use, geofences are still checked, but only while the app is open.
- Precise location on iOS for iOS to watch the geofences itself. Without it, entering and leaving are decided from location changes, which is coarser.
Ask with Bubbl.permissions.requestLocation and always set to true. On Android 11 and later "Allow all the time" is a separate second step; on iOS the second step goes from "While Using" to "Always". Bubbl asks for both. See Permissions and privacy.
val status = Bubbl.permissions.requestLocation(always = true) // from a coroutinelet status = await Bubbl.permissions.requestLocation(always: true)final status = await Bubbl.permissions.requestLocation(always: true);const status = await Bubbl.permissions.requestLocation({ always: true });If your app already asks for location itself, you don't have to use Bubbl.permissions: Bubbl uses whatever permission your app holds.
Android#
The SDK's manifest declares ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION and ACCESS_BACKGROUND_LOCATION, plus RECEIVE_BOOT_COMPLETED so geofences come back after a reboot. You don't add them yourself.
The Play Console declaration#
Because the SDK declares ACCESS_BACKGROUND_LOCATION, every app using Bubbl fills in the Location permissions declaration in the Play Console, with a short video of the feature. Google Play also requires a prominent disclosure before the system prompt. requestLocation(always = true) shows Bubbl's privacy view first when the dashboard's privacy view is set to Show automatically or Always show. If you set it to Never show, or ask for background location yourself, show your own disclosure before asking.
How Android watches#
Google Play services watches up to 50 geofences from the server plus a refresh circle (Play services allows 100 per app, shared with your app's own). The SDK also uses location fixes other apps already asked for, and checks the geofences every 15 minutes in the background. Geofences are registered again after a reboot, an app update, and location being turned off and on.
iOS#
Add both location usage strings to your app's Info.plist: NSLocationWhenInUseUsageDescription and NSLocationAlwaysAndWhenInUseUsageDescription. You don't need any Background Modes for geofences: region monitoring and significant location changes relaunch the app without them.
How iOS watches#
iOS allows 20 monitored regions per app, shared by your app and every SDK in it. Bubbl watches up to 19 of the nearest geofences plus one refresh region, and takes fewer when iOS says the app is over its limit. Bubbl's regions are named bubbl.…. It also uses significant location changes and checks at every app open. When "Always" is asked for straight away, iOS grants it only provisionally at first.
Background refresh (optional)#
Without it, Bubbl sends what it has queued (events, device changes) when the app goes to the background and on its next run. With it, iOS also wakes the app now and then to send them. In Info.plist, add tech.bubbl.sdk.refresh to BGTaskSchedulerPermittedIdentifiers and fetch to UIBackgroundModes (Signing & Capabilities › Background Modes › Background fetch). Bubbl registers the task itself at launch, and only when both are there. In Expo, set "backgroundRefresh": true under ios in Bubbl's config plugin options.
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>tech.bubbl.sdk.refresh</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
</array>If your app already has either key, add the value to its array.
Turning location off#
setLocationEnabled(false) stops Bubbl's use of location: its geofences are removed and nothing is checked. The rest of Bubbl carries on, pushes and events included. setLocationEnabled(true) turns it back on.
Bubbl.setLocationEnabled(false)Bubbl.setLocationEnabled(false)await Bubbl.setLocationEnabled(false);Bubbl.setLocationEnabled(false);Hearing about geofences#
Your app can listen for geofences entered and left, with the location's ID. These are for your own use; Bubbl records its own.
Bubbl.addEventListener { event ->
when (event) {
is BubblEvent.GeofenceEntered -> Log.i("App", "Entered ${event.locationId}")
is BubblEvent.GeofenceExited -> Log.i("App", "Left ${event.locationId}")
else -> {} // new event types arrive in minor releases
}
}let token = Bubbl.addEventListener { event in
switch event {
case .geofenceEntered(let locationId): print("Entered \(locationId)")
case .geofenceExited(let locationId): print("Left \(locationId)")
default: break // new event types arrive in minor releases
}
}final subscription = Bubbl.addEventListener((event) {
switch (event) {
case BubblGeofenceEntered(:final locationId):
debugPrint('Entered $locationId');
case BubblGeofenceExited(:final locationId):
debugPrint('Left $locationId');
default:
break; // new event types arrive in minor releases
}
});const subscription = Bubbl.addEventListener((event) => {
switch (event.type) {
case 'geofence.entered': console.log('Entered', event.locationId); break;
case 'geofence.exited': console.log('Left', event.locationId); break;
default: break; // new event types arrive in minor releases
}
});See Segments and events for the other events.
Checking it in diagnostics#
geofences: how many are watched.backgroundLocation: true with "Always" or "Allow all the time". If false, geofences work only while the app is open.locationEnabled: false aftersetLocationEnabled(false).osWatchesGeofences(iOS): true when iOS watches the geofences itself. If false, they're checked from location changes.backgroundRestricted(Android): true when the user restricted the app's background activity, so Bubbl can't run with the app closed.transitionsDroppedWhileLocked(iOS): geofence events lost before the first unlock after a reboot. Always 0 on Android.
See Testing and diagnostics and Diagnostics.
Limits#
| Android | iOS | |
|---|---|---|
| Geofences watched | Up to 50 plus a refresh circle | Up to 19 of the nearest plus a refresh region |
| Without background location | Checked while the app is open | Checked while the app is open |
| Events before the first unlock after a reboot | Delivered once the phone is unlocked | Lost, and counted in transitionsDroppedWhileLocked |
On Android, after the user force-stops your app (Settings › Apps › Force stop), Android wakes nothing of it until it's opened again, so geofences don't fire in the meantime.
When geofences are checked from location fixes, fixes less accurate than 200 m are ignored, and leaving only counts once the distance minus the fix's accuracy is more than the geofence's radius, so a jittery fix at the edge doesn't flap in and out.