Permissions & background location
Background location on Android is not one permission — it is three grants
collected in a fixed order, and asking for them the obvious way fails
silently. The SDK drives the whole sequence, but it needs one object from your
Activity and it cannot create that object itself.
Give it an ActivityResultCaller
class MainActivity : ComponentActivity() { private lateinit var permissionRequester: PermissionRequester
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Must be constructed BEFORE this Activity reaches STARTED: // registerForActivityResult (which this calls internally) requires it. permissionRequester = PermissionRequester(this) }}Construct it in onCreate, not lazily on a button tap. PermissionRequester
registers an ActivityResultLauncher in its constructor, and the Activity
Result API rejects a registration made after the host has started — the crash
message names registerForActivityResult, which is easy to misread as a
problem with the SDK rather than with when you called it.
Then hand it to the request:
lifecycleScope.launch { val status = BackgroundGeolocation.requestPermission(permissionRequester) Log.d("BGeo", "authorization: $status")}What it actually asks for, and in what order
1. FOREGROUND ACCESS_FINE_LOCATION + ACCESS_COARSE_LOCATION2. BACKGROUND ACCESS_BACKGROUND_LOCATION (API 29+)3. ACTIVITY_RECOGNITION ACTIVITY_RECOGNITION (API 29+)One stage per request, never bundled. From API 30 onward Android silently denies a request that asks for foreground and background location together: no dialog, no error, an immediate “denied” for both. The system requires foreground location to be granted first, and background location to be asked for separately afterwards — which is what the SDK does.
Stage 2 is skipped when your config asks for foreground-only tracking
(locationAuthorizationRequest = "WhenInUse"), and stages 2 and 3 do not
exist below API 29.
A denial does not stop the chain. Each stage is attempted once and the escalation moves on regardless of outcome. A user who refuses “Allow all the time” still gets asked for activity recognition, because that grant meaningfully improves tracking on its own. Bailing out at the first “no” would silently cost you a permission the user would have granted.
What you get back
requestPermission() resolves to an AuthorizationStatus:
| Value | Meaning |
|---|---|
ALWAYS | Background location granted — tracking works with the app closed. |
WHEN_IN_USE | Foreground only — see the degradation below. |
DENIED | No location grant; tracking cannot produce fixes. |
NOT_DETERMINED | Nothing asked yet. |
After the escalation finishes the SDK resumes tracking if a start() had
earlier bailed for want of permission, and emits a
onProviderChange event so subscribers see
the final grant state without polling.
Degradation, stage by stage
Without background location (WHEN_IN_USE), Android delivers location only
while your app is in the foreground. The engine keeps working — the queue, the
filter, the motion machine are all unaffected — but fixes stop arriving when the
user leaves your app, and resume when they return. Nothing announces this; the
stream simply goes quiet.
Without activity recognition, the motion state machine falls back to speed-based detection and the stationary geofence. The practical cost is a slower, less certain transition out of “stationary”: instead of the OS telling the engine that the user started walking, the engine waits for either GPS speed or a ~200 m displacement from the stationary anchor.
Without any location grant, start() still succeeds and the engine still
runs — it just never receives a fix. Check getState().authorization (or the
onProviderChange event) rather than assuming the SDK is broken.
Foreground service and notifications
Tracking runs in a foreground service, and on API 33+ the user must also grant
POST_NOTIFICATIONS for its notification to be visible. The service runs either
way; the notification is what the user sees. The SDK does not request this
permission — it is your app’s notification channel to explain, and most apps
already ask for it elsewhere.
Configure the notification through the notification.*
config keys.
Asking at the right moment
Android shows the background-location dialog exactly once per app install in
practice: refuse it, and later requests go straight to “denied” without any UI.
Ask when the user has just done something that explains why you need it, not on
first launch. If they have already refused, requestPermission() returns
without a dialog — send them to system settings instead, which is why the SDK
does not pop a “go to settings” alert of its own.