-
Notifications
You must be signed in to change notification settings - Fork 281
[Bug]: addGeofence() in headless mode on Android reports success but OS Geofencer does not actively monitor the registration #1653
Description
Required Reading
- Confirmed
Plugin Version
5.0.5
Flutter Doctor
[✓] Flutter (Channel stable, 3.41.2, on macOS 26.3 25D125 darwin-arm64, locale en-GB)
[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0)
[✓] Xcode - develop for iOS and macOS (Xcode 26.3)
[✓] Chrome - develop for the web
[✓] Connected device (3 available)
[✓] Network resources
• No issues found!Mobile operating-system(s)
- iOS
- Android
Device Manufacturer(s) and Model(s)
Google Pixel 8
Device operating-systems(s)
Android 16
What happened?
Expected Behavior
When addGeofence() is called from a headless task (app terminated), the newly registered geofence should be actively monitored by the Android OS Geofencer, and EXIT events should fire when the device leaves the geofence radius.
Actual Behavior
addGeofence() succeeds (returns without error, plugin logs ✅ SEEKERAPP_FINE) but the Android OS Geofencer logs "registration not active, registration not permitted" for the newly added geofence. The geofence is not actively monitored — EXIT events do not fire until the OS Geofencer's low-power batch evaluation eventually picks them up (~2–7 minutes later).
This creates a significant gap in geofence tracking reliability when the app is terminated.
Steps to Reproduce
- Configure plugin with
stopOnTerminate: false,enableHeadless: true,geofenceModeHighAccuracy: false - Start monitoring with
startGeofences() - Register initial geofences with
addGeofence() - Terminate the app (swipe kill)
- Move the device to trigger a geofence EXIT event
- In the headless task handler, call
addGeofence()to register a new geofence at the updated location - Continue moving — observe that EXIT events for the newly registered geofence are significantly delayed or missing
Debug Logs
Relevant logcat excerpts showing the issue:
Foreground geofences work correctly
16:26:13.717 TSLocationManager: ✅ SEEKERAPP_COARSE
16:26:13.718 TSLocationManager: ✅ SEEKERAPP_FINE
16:26:13.762 GeofenceProxy: Batched PI received for ...
After foreground addGeofence(), the OS Geofencer immediately shows "Batched PI received" confirming active monitoring.
App terminated, headless addGeofence() — OS rejects registration
16:27:14.504 TSLocationManager: ║ Geofencing Event: EXIT
16:27:14.504 TSLocationManager: ╟─ SEEKERAPP_FINE
16:27:58.508 [GeofenceHeadless] geofence EXIT: SEEKERAPP_FINE
16:27:59.065 TSLocationManager: ✅ SEEKERAPP_FINE <-- addGeofence() reports success
16:28:00.017 GeofenceProxy: registration not active, registration not permitted.
16:28:00.017 GeofenceProxy: Batched PI received for ... <-- note: this is evaluation of OLD registration
After headless addGeofence():
- Plugin reports success (
✅) - OS Geofencer logs "registration not active, registration not permitted"
- No EXIT events fire for 7+ minutes until the OS Geofencer's low-power sweep picks up the registration
7-minute blackout period
16:28:00.017 Last "Batched PI" evaluation
...
16:35:02.024 Next "Batched PI" evaluation (7 minutes later)
During this gap, the device continued moving through multiple geofence boundaries, but no EXIT events fired.
Root Cause Analysis
The issue appears to be that addGeofence() in headless mode updates the plugin's internal geofence state and calls the platform GeofencingClient.addGeofences() API, but the Android OS Geofencer does not honour the registration because the app's monitoring adapter is not in an "active" state.
The key evidence is the OS Geofencer log: "registration not active, registration not permitted". This indicates the OS acknowledges the addGeofences() API call but does not activate monitoring for it. The plugin, however, reports success because the API call itself did not throw an error.
Why startGeofences() cannot be called in headless mode
We initially tried calling startGeofences() after addGeofence() in the headless handler. However:
startGeofences()internally callsstop()thenstart()(or equivalent)- In headless mode,
stop()kills the existing foreground monitoring session startGeofences()then fails withForegroundServiceStartNotAllowedExceptionon Android 12+ because background-launched processes cannot start foreground services- Result: monitoring is permanently dead until the user opens the app
Our current workaround
We now call startGeofences() without stop() first:
// After addGeofence() in headless handler:
try {
await bg.BackgroundGeolocation.startGeofences();
} catch (e) {
// Expected on Android 12+ in headless mode.
// No stop() was called, so existing monitoring is preserved.
}This sometimes succeeds in refreshing the monitoring adapter. When it fails, the existing monitoring state is preserved (since we didn't call stop()). We also use the heartbeat callback as a fallback to detect and correct missed geofence transitions.
Suggested Fix
The plugin should detect when addGeofence() is called in headless mode and either:
- Automatically refresh the monitoring adapter after
addGeofence()without a full stop/start cycle (similar to our workaround above) - Return a warning or different status when the OS reports "registration not active" so callers know the geofence may not be actively monitored
- Document the limitation that
addGeofence()in headless mode may not result in active monitoring on Android 12+
Context
We are building a location-aware mobile app that uses adjacent cells for geofencing. When a user exits one cell, we register a geofence for the new cell. This works perfectly in foreground/background but breaks when the app is terminated because the headless addGeofence() calls don't result in active OS monitoring.
The heartbeat callback (60s interval) serves as our fallback for detecting missed transitions, but the 7-minute blackout period after headless geofence registration significantly degrades the user experience.
Plugin Code and/or Config
bg.BackgroundGeolocation.ready(bg.Config(
desiredAccuracy: bg.Config.DESIRED_ACCURACY_HIGH,
distanceFilter: 0,
stopOnTerminate: false,
enableHeadless: true,
startOnBoot: true,
heartbeatInterval: 60,
preventSuspend: true,
geofenceModeHighAccuracy: false,
foregroundService: true,
));
bg.BackgroundGeolocation.startGeofences();Relevant log output
See main report