Required Reading
Plugin Version
5.5.0
Flutter Doctor
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 3.44.7, on Microsoft Windows [version 10.0.26200.9168], locale fr-CH)
[√] Windows Version (Windows 11 or higher, 25H2, 2009)
[√] Android toolchain - develop for Android devices (Android SDK version 36.1.0)
[√] Chrome - develop for the web
[√] Visual Studio - develop Windows apps (Visual Studio Community 2026 18.9.1)
[√] Connected device (3 available)
[√] Network resources
• No issues found!
Mobile operating-system(s)
Device Manufacturer(s) and Model(s)
iPhone 17 Pro
Device operating-systems(s)
iOS 26.6
What happened?
Environment
- Plugin:
flutter_background_geolocation 5.5.0 (Flutter)
- Platform: iOS (iPhone 17 Pro, iOS 26.6)
- The call:
bg.BackgroundGeolocation.getCurrentPosition(
samples: 1,
timeout: 30,
persist: false,
// maximumAge not passed → default 0
);
Summary
During a day-long recorded activity, getCurrentPosition repeatedly returned a
location almost 3 hours old and ~130 km away (the morning departure point),
even though:
maximumAge was 0 (the documented default, we expected "no cached fix
accepted", but the plugin appears to treat 0 as "no age constraint"), and
- a fresh fix had been delivered 29 seconds before the call (and 200+
location updates had been received since the stale fix was recorded).
The stale location is served via the request-service cache path, the native
log explicitly shows [CACHED] getCurrentPosition satisfied from lastLocation.
Relation to issue #1691
This is the same symptom family as
#1691
(iOS getCurrentPosition returning locations older than maximumAge since
v5.x), which was auto-closed as stale. #1691's discussion attributed the stale
result to GPS acquisition delay ("it takes time to connect to satellites /
there is no GPS indoors"). Our log rules that explanation out: a fresh
outdoor fix (±3.82 m, age: 79 ms) was delivered 29 seconds before the
getCurrentPosition call, yet the plugin still served a fix recorded 2 h 52
earlier and 130 km away. The SDK wasn't waiting for GPS, its request-service
lastLocation was simply frozen on an old-but-very-accurate sample (see
Analysis below), which also explains why samples: 2 only mitigated #1691
instead of fixing it.
Timeline (from the TSLocationManager native log, 22 Aug 2026, times CEST)
08:18:52 — motionchange at the day's departure point ("home", coords
redacted below as 46.25x/6.07x). This fix — ±2.00 m accuracy — is the one
that will be resurrected all day.
08:18 → 11:10 — a ~2 h drive + the start of a hike are tracked normally:
200+ didUpdateLocations deliveries, e.g. still flowing 29 s before the
problematic call:
2026-08-22 11:10:19.186 📍<+46.69561031,+7.86718045> +/- 3.82m (speed 0.58 mps / course 281.31) @ 22.08.26 11.10:19 | age: 79 ms
11:10:48 — the app calls getCurrentPosition (samples: 1, timeout: 30,
persist: false). The device is outdoors, on a mountain trail with clear sky
view (the fresh ±3.82 m fix 29 s earlier proves GPS reception). The plugin
serves the 08:18:52 fix — 2 h 52 old, ~130 km from the device's actual
position:
2026-08-22 11:10:48.478 🔵 -[TSTrackingService getCurrentPosition:]
2026-08-22 11:10:48.478 🟢 -[TSLocationRequestService requestLocation:] [getCurrentPosition] maximumAge: 0
2026-08-22 11:10:48.478
╔═══════════════════════════════════════════════════════════
║ -[TSSingleLocationRequest trySatisfyWithLocation:now:] 📍🔎 [getCurrentPosition] desiredAccuracy: 5.0 m, maximumAge: 0 ms
╚═══════════════════════════════════════════════════════════
2026-08-22 11:10:48.479 🔵 +[TSLocationHelper pickBestLocationBetween:and:desiredAccuracy:] desiredAccuracy: 5.000000
- A: 📍<+46.25xxxxxx,+6.07xxxxxx> +/- 2.00m (speed 7.62 mps / course 354.34) @ 22.08.26 08.18:52
- B: 📍<+46.25xxxxxx,+6.07xxxxxx> +/- 2.00m (speed 7.62 mps / course 354.34) @ 22.08.26 08.18:52
2026-08-22 11:10:48.479 🔵 -[TSLocationRequestService requestLocation:]_block_invoke [CACHED] getCurrentPosition satisfied from lastLocation
11:23:41 — same call, same result (the 08:18:52 fix again, now 3 h 05 old):
2026-08-22 11:23:41.022 🟢 -[TSLocationRequestService requestLocation:] [getCurrentPosition] maximumAge: 0
2026-08-22 11:23:41.022 🔵 +[TSLocationHelper pickBestLocationBetween:and:desiredAccuracy:] desiredAccuracy: 5.000000
- A: 📍<+46.25xxxxxx,+6.07xxxxxx> +/- 2.00m ... @ 22.08.26 08.18:52
2026-08-22 11:23:41.022 🔵 -[TSLocationRequestService requestLocation:]_block_invoke [CACHED] getCurrentPosition satisfied from lastLocation
The same frozen location also drives geofence evaluation:
2026-08-22 11:10:48.795 🔵 -[TSGeofenceManager onEvaluate] - enabled? 1, _lastLocation? <+46.25xxxxxx,+6.07xxxxxx> +/- 2.00m ... @ 22.08.26 08.18:52
Analysis (as far as we can see from the log)
Two compounding behaviors:
-
maximumAge: 0 seems to mean "disabled", not "must be fresh".
trySatisfyWithLocation prints maximumAge: 0 ms and still satisfies the
request from a fix recorded 2 h 52 earlier. If 0 is intentionally "no
constraint", the docs don't make that clear and there is then no way to
ask for a strictly-fresh sample other than passing maximumAge: 1.
-
The request-service lastLocation never updates to fresher-but-less-
accurate fixes. The stale 08:18:52 fix has ±2.00 m; the fresh fixes
around 11:10 have ±3.82–4.46 m. pickBestLocationBetween (called with
desiredAccuracy: 5.0) appears to compare candidates on accuracy only,
with no freshness term so once a very accurate fix is cached, hours of
subsequent (slightly less accurate) fixes never replace it, and with
maximumAge disabled it is served as "current" indefinitely.
Impact on our app
The resurrected fix is also broadcast to onLocation subscribers, so it:
- landed inside an actively-recorded trip (a 130 km teleport segment in the
polyline),
- was returned to a user-triggered "check-in" feature, which recorded the user
at their morning departure point instead of their actual location,
- kept re-centering the live map on the stale position after every pause.
Workaround we deployed
- Pass an explicit
maximumAge: 60000 on every getCurrentPosition call, and
- verify
location.age on every result / onLocation event in Dart, dropping
anything older than 2 minutes.
Expected behavior
getCurrentPosition with maximumAge: 0 (or at least with a small explicit
maximumAge) should never be satisfied from a cache older than that age,
it should sample, and the "best location" choice should weigh freshness,
not accuracy alone.
Full (unredacted) native log available on request the excerpts above redact
only the home coordinates.
Plugin Code and/or Config
bg.BackgroundGeolocation.ready(bg.Config(
reset: true,
geolocation: bg.GeoConfig(
geofenceProximityRadius: 1000,
geofenceInitialTriggerEntry: false,
geofenceModeHighAccuracy: true, // Android-only; no-op on iOS
desiredAccuracy: bg.DesiredAccuracy.high,
filter: const bg.LocationFilter(
policy: bg.LocationFilterPolicy.adjust,
useKalman: true,
trackingAccuracyThreshold: 50,
maxImpliedSpeed: 60,
),
stationaryRadius: 50,
distanceFilter: 10,
showsBackgroundLocationIndicator: true,
activityType: bg.ActivityType.other,
locationUpdateInterval: 30000,
fastestLocationUpdateInterval: 10000,
elasticityMultiplier: 1.0,
stopTimeout: 3,
locationAuthorizationAlert: {/* localized strings */},
disableLocationAuthorizationAlert: true,
),
app: bg.AppConfig(
stopOnTerminate: false,
startOnBoot: true,
enableHeadless: true,
notification: bg.Notification(
title: 'Xxx', text: 'Tracking active',
channelName: 'Location', sticky: false,
priority: bg.NotificationPriority.low,
smallIcon: 'drawable/ic_stat_xxx', color: '#4D2ADD',
),
),
backgroundPermissionRationale: bg.PermissionRationale(/* strings */),
locationAuthorizationRequest: 'WhenInUse',
logger: bg.LoggerConfig(
logLevel: bg.LogLevel.verbose, // verbose was ON during the incident
logMaxDays: 3,
),
));
// Right after ready(): if the OS already granted Always, we realign via
// setConfig({locationAuthorizationRequest: 'Always'}). 14 geofences
// (hysteresis pairs) are registered at startup. iOS "Always" was granted.
Relevant log output
Required Reading
Plugin Version
5.5.0
Flutter Doctor
Mobile operating-system(s)
Device Manufacturer(s) and Model(s)
iPhone 17 Pro
Device operating-systems(s)
iOS 26.6
What happened?
Environment
flutter_background_geolocation5.5.0 (Flutter)Summary
During a day-long recorded activity,
getCurrentPositionrepeatedly returned alocation almost 3 hours old and ~130 km away (the morning departure point),
even though:
maximumAgewas 0 (the documented default, we expected "no cached fixaccepted", but the plugin appears to treat 0 as "no age constraint"), and
location updates had been received since the stale fix was recorded).
The stale location is served via the request-service cache path, the native
log explicitly shows
[CACHED] getCurrentPosition satisfied from lastLocation.Relation to issue #1691
This is the same symptom family as
#1691
(iOS
getCurrentPositionreturning locations older thanmaximumAgesincev5.x), which was auto-closed as stale. #1691's discussion attributed the stale
result to GPS acquisition delay ("it takes time to connect to satellites /
there is no GPS indoors"). Our log rules that explanation out: a fresh
outdoor fix (
±3.82 m,age: 79 ms) was delivered 29 seconds before thegetCurrentPositioncall, yet the plugin still served a fix recorded 2 h 52earlier and 130 km away. The SDK wasn't waiting for GPS, its request-service
lastLocationwas simply frozen on an old-but-very-accurate sample (seeAnalysis below), which also explains why
samples: 2only mitigated #1691instead of fixing it.
Timeline (from the TSLocationManager native log, 22 Aug 2026, times CEST)
08:18:52 — motionchange at the day's departure point ("home", coords
redacted below as
46.25x/6.07x). This fix —±2.00 maccuracy — is the onethat will be resurrected all day.
08:18 → 11:10 — a ~2 h drive + the start of a hike are tracked normally:
200+
didUpdateLocationsdeliveries, e.g. still flowing 29 s before theproblematic call:
11:10:48 — the app calls
getCurrentPosition(samples: 1, timeout: 30,persist: false). The device is outdoors, on a mountain trail with clear sky
view (the fresh
±3.82 mfix 29 s earlier proves GPS reception). The pluginserves the 08:18:52 fix — 2 h 52 old, ~130 km from the device's actual
position:
11:23:41 — same call, same result (the 08:18:52 fix again, now 3 h 05 old):
The same frozen location also drives geofence evaluation:
Analysis (as far as we can see from the log)
Two compounding behaviors:
maximumAge: 0seems to mean "disabled", not "must be fresh".trySatisfyWithLocationprintsmaximumAge: 0 msand still satisfies therequest from a fix recorded 2 h 52 earlier. If 0 is intentionally "no
constraint", the docs don't make that clear and there is then no way to
ask for a strictly-fresh sample other than passing
maximumAge: 1.The request-service
lastLocationnever updates to fresher-but-less-accurate fixes. The stale 08:18:52 fix has
±2.00 m; the fresh fixesaround 11:10 have
±3.82–4.46 m.pickBestLocationBetween(called withdesiredAccuracy: 5.0) appears to compare candidates on accuracy only,with no freshness term so once a very accurate fix is cached, hours of
subsequent (slightly less accurate) fixes never replace it, and with
maximumAgedisabled it is served as "current" indefinitely.Impact on our app
The resurrected fix is also broadcast to
onLocationsubscribers, so it:polyline),
at their morning departure point instead of their actual location,
Workaround we deployed
maximumAge: 60000on everygetCurrentPositioncall, andlocation.ageon every result /onLocationevent in Dart, droppinganything older than 2 minutes.
Expected behavior
getCurrentPositionwithmaximumAge: 0(or at least with a small explicitmaximumAge) should never be satisfied from a cache older than that age,it should sample, and the "best location" choice should weigh freshness,
not accuracy alone.
Full (unredacted) native log available on request the excerpts above redact
only the home coordinates.
Plugin Code and/or Config
Relevant log output