NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
pub.dev
Background route tracking for iOS and Android, collected and uploaded in native code.
Last release 25 days ago
13 Sep 2026
Ships on a steady schedule
a new release about every 1 weeks
Nearly every release is documented
notes for 10 of 10 stable releases
Nothing withdrawn
no release was ever pulled
2 months old
10 releases · first in 2026
One column per month.
Power modes: `powerSave`, `balanced`, `high`. A session used to collect at one intensity for as long as it ran — the GPS chip at the configured filter
powerSave, balanced, high. A session used to collect
at one intensity for as long as it ran — the GPS chip at the configured
filter, whether or not anybody was looking. It now runs the lowest of three
answers: what the app asks for with the new setPowerMode, the new
GeoUploadConfig.ceiling, and powerSave while the OS battery saver is on.
high is the configuration exactly as given; balanced switches to Wi-Fi
and cell positioning (~100 m, every 30 s / 50 m, uploaded once a minute);
powerSave takes FusedLocation every five minutes on Android and only
significant location changes on iOS, uploading each accepted point at once.
Every preset is max(preset, configured), so a lower mode is never tighter
than high.balanced — a high asked for by an open map cannot outlive the
app being swiped away. The ceiling is persisted with the rest of the config.GeoTrackingStatus gains powerMode and requestedPowerMode. A
power.mode log line records every change of the effective mode and why.GeoUploadConfig's full constructor takes a required
ceiling; GeoUploadConfig.standard defaults it to high.Finish the loopback-HTTP rule in the native config stores. 0.12.0 relaxed it in GeoUploadConfig only, so http://localhost cleared the Dart check and w
GeoUploadConfig only, so http://localhost cleared the Dart
check and was then refused by Android's and iOS' own validators — the same
URL rejected twice under two different error shapes. Both now apply the rule
the Dart side does: HTTPS anywhere, plain HTTP only to a loopback host, LAN
addresses still refused. Literal hosts only on both sides, never a name
lookup — resolving one inside a validator would mean a network call on the
platform thread.Ship the minIntervalSeconds: 0 fix that the 0.12.0 notes described. The Dart-side _requirePositive check was never removed in 0.12.0, so zero was stil
minIntervalSeconds: 0 fix that the 0.12.0 notes described. The
Dart-side _requirePositive check was never removed in 0.12.0, so zero was
still rejected despite both native sides reading it as "no interval floor"Plain HTTP is accepted for a loopback host. http://localhost, http://127.0.0.1 and http://[::1] now pass the URL check, so a backend running on the de
http://localhost,
http://127.0.0.1 and http://[::1] now pass the URL check, so a backend
running on the developer's own machine no longer needs a certificate to be
talked to. Everything else still has to be HTTPS — a LAN address included,
because a phone reaching a laptop over Wi-Fi is putting someone's
whereabouts and an upload credential on a shared network.minIntervalSeconds: 0 is accepted again. Zero means "no interval
floor", the same way distanceFilterMeters: 0 means "no distance floor",
and both native sides already read it that way — iOS compares a fix against
a zero floor so only an out-of-order one is dropped, Android hands it to
LocationRequest as "as fast as the platform will provide". The Dart-side
check demanded a positive number and was the only thing rejecting it, which
left a caller who wanted every fix unable to say so.Removed the last `Attractor`/`attractor_geo` naming — the Android package (school.attractor.attractor_geo → dev.background_geo_tracker.plugin), the At
Attractor/attractor_geo naming — the Android
package (school.attractor.attractor_geo → dev.background_geo_tracker.plugin),
the AttractorGeoPlugin/AttractorGeoLaunch classes (now
BackgroundGeoTrackerPlugin/BackgroundGeoTrackerLaunch), the method
channel names, and every on-device storage identifier (SQLite filenames,
the Keychain service, the UserDefaults prefix, the SharedPreferences
files, the notification channel and the WorkManager job names) now say
background_geo_tracker throughout. Unlike the 0.3.0 rename, this one is
breaking for existing installs: upgrading orphans any queued points and
stored credentials, and an already-scheduled Android job from an older
version has to be cancelled by hand, because none of the old identifiers
survive.BackgroundGeoTrackerLaunch.resumeIfTracking().
Nothing about the Dart API moved.`stopTimeoutSeconds: 0` disables stop detection. The GPS stays on for the whole session. Previously there was no way to turn the state machine off: yo
stopTimeoutSeconds: 0 disables stop detection. The GPS stays on for
the whole session. Previously there was no way to turn the state machine
off: you had to set an absurdly large timeout and hope it was never
reached. Zero means "disabled", not "stop immediately" — the second
reading would have killed collection on the second fix of every session.
The same convention as elasticityMultiplier: 0.distanceFilterMeters: 0 now really means "no filter".
kCLDistanceFilterNone is -1, and CoreLocation does not define zero: in
practice the delegate simply went silent. The configuration asked for
every fix and got silence — the worst kind of platform divergence, because
Android worked fine with the same settings. Zero and below are now
translated to the constant.Breaking: `GeoUploadConfig` takes one `url`, not `baseUrl` and `path`. The uploader never had a use for the two halves apart — it joined them back tog
Breaking: GeoUploadConfig takes one url, not baseUrl and path.
The uploader never had a use for the two halves apart — it joined them back
together and posted to the result. All the split bought was a way to be
wrong about who owns the slash between them, re-answered by every caller,
with the caller that got it wrong finding out as a 404 inside a background
uploader nobody watches. Callers pass the finished endpoint:
GeoUploadConfig.standard(url: 'https://api.example.com/v1/points', …)
The method-channel key changes with it — base_url and path become url
— and both platforms read only the new one. Nothing reads the old pair and
nothing migrates it: an install carrying a stored config from an earlier
build reads an empty URL and posts nowhere until something calls configure
again. Reinstall, or make the host re-configure on launch.
The status says why the queue is not draining. GeoTrackingStatus gains
uploadUrl — the endpoint the native uploader holds, which is not always
what the app believes it configured — and lastUpload, how the last drain
ended: ok, no url — never configured, http 500, network: …,
halted: credentials refused. The uploader gives up at five separate guards
and did so in silence at every one, which made a growing queue on an
otherwise healthy-looking collector impossible to explain from a log. Both
fields decode with fallbacks, so a platform that has not implemented them
still produces a usable status.
Android: an unusable endpoint no longer kills the worker. Request.url
throws IllegalArgumentException, which the worker's IOException catch
never covered, so an empty URL took the drain down with nothing to show for
it. Parsed up front now, and recorded as lastUpload.
iOS: a session comes back after a reboot again. The relaunch path ran entirely through the plugin's own application delegate, and a plugin is register
FlutterViewController exists. In a
scene-based app that view controller is instantiated when a UI scene
connects — and iOS waking the app in the background for a significant
location change connects none. So on the one launch the whole path was built
for, nothing ran: no CLLocationManager, no drain, no points. A phone
switched off and back on stayed silent, and the position the backend served
for it was the one from the moment it powered down, until its owner opened
the app by hand.AttractorGeoLaunch.resumeIfTracking() from
your AppDelegate's application(_:didFinishLaunchingWithOptions:). That
method is what a background launch does call. See the iOS setup section of
the README, which until now said no AppDelegate changes were needed.First release on pub.dev. Nothing about how tracking works changed; everything below is naming, packaging and documentation.
First release on pub.dev. Nothing about how tracking works changed; everything below is naming, packaging and documentation.
attractor_geo to background_geo_tracker, and
AttractorGeoController to BackgroundGeoTracker with it. The old name said
who wrote the package rather than what it does, which is no use to anyone
finding it on pub.dev. Update the dependency, the import and the one class
name; nothing else about the API moved.attractor_geo* — the SQLite
filenames, the Keychain service, the UserDefaults prefix, the
SharedPreferences files, the notification channel and the WorkManager job
names. Renaming them would orphan the queue and the stored credentials of
every install that upgrades, and leave an already-scheduled Android job that
the new code no longer knows how to cancel. An upgrade from 0.2.0 keeps its
undelivered points.reset() on
sign-out and re-configure() on start() are now stated as the host's job,
because outside that app nothing else does them for you..pubignore, MIT licence and pub.dev metadata.Added `reset()` — ends the session and forgets what belonged to the account that was signed in: the stored upload credentials and every queued point.
reset() — ends the session and forgets what belonged to the
account that was signed in: the stored upload credentials and every queued
point. stop() deliberately keeps both. Without this the credentials and the
queue outlived a sign-out, and the next account on the device would upload
the previous one's track under its own name.statusChanges never
learned that a session had started, that a permission dialog had been
answered, or that permission had been revoked mid-session — while iOS
reported all of them. Answered permission dialogs are picked up through a
RequestPermissionsResultListener, which the plugin did not register at all.POST_NOTIFICATIONS once at start, on API 33+. The
foreground service's notification is required to be visible for the whole
session and did not appear without the runtime grant. Refusing it hides the
notification and does not stop the session.uploadIntervalSeconds. The interval was served by
WorkManager, whose floor for periodic work is 15 minutes, so a phone sitting
still under batchSize held its points for a quarter of an hour while iOS
shipped them in a minute — one setting meaning two different things. The
foreground service, alive for the whole session anyway, now drives the drain
itself and only enqueues when the queue is non-empty. The periodic worker
stays as the fallback for a collector the OS has killed.OkHttpClient across worker runs on Android, instead of one per run
throwing away the connection pool.Your coding agent can read these notes before it upgrades. Set up the MCP server →