Android: base activity tracker — parity port of iOS v1 (epic #983) #987

Closed
opened 2026-08-06 20:00:35 +00:00 by spikerj · 2 comments
Owner

Part of epic #983. Android currently has NO tracker (no location permission, no map dependency, no tracking code).

Port the iOS v1 tracker to Kotlin/Compose, file-for-file:

  • Models + TrackStats exact port, with TrackStatsTest pinning the SAME fixtures as iOS TrackStatsTests.swift (111.195 km / pause fixture / formatDuration strings) — creates the repo's first unit-test source set and finally makes the "keep all three stacks in sync" comment true.
  • RouteStore (file-per-route JSON + crash snapshot + delete tombstones), FusedLocationClient, ActivityRecorder (exact ingest filter: acc>50 reject, ≥2s AND ≥5m per segment), foreground TrackingService (type=location, while-in-use only — NO background-location permission), RouteSyncService (single-flight upload + tombstone flush, 404 settles).
  • MapLibre Android with the iOS TrackerStyle.json copied verbatim; Compose Tracker/History/Detail screens + nav entry; Hilt module; prominent-disclosure dialog before permission ask (Play policy).
  • Deps: maplibre android-sdk, play-services-location, lifecycle-process.

Ghost race mode on Android is a FOLLOW-UP after this lands (engine ports from iOS GhostComparator).

Part of epic #983. Android currently has NO tracker (no location permission, no map dependency, no tracking code). Port the iOS v1 tracker to Kotlin/Compose, file-for-file: - Models + `TrackStats` exact port, with `TrackStatsTest` pinning the SAME fixtures as iOS `TrackStatsTests.swift` (111.195 km / pause fixture / formatDuration strings) — creates the repo's first unit-test source set and finally makes the "keep all three stacks in sync" comment true. - `RouteStore` (file-per-route JSON + crash snapshot + delete tombstones), `FusedLocationClient`, `ActivityRecorder` (exact ingest filter: acc>50 reject, ≥2s AND ≥5m per segment), foreground `TrackingService` (type=location, while-in-use only — NO background-location permission), `RouteSyncService` (single-flight upload + tombstone flush, 404 settles). - MapLibre Android with the iOS `TrackerStyle.json` copied verbatim; Compose Tracker/History/Detail screens + nav entry; Hilt module; prominent-disclosure dialog before permission ask (Play policy). - Deps: maplibre android-sdk, play-services-location, lifecycle-process. Ghost race mode on Android is a FOLLOW-UP after this lands (engine ports from iOS GhostComparator).
Author
Owner

Premise correction: this ticket said "Android has NO tracker" — that was read from a stale local checkout (8 commits behind). spikersoft-android PR #3 (merged 2026-08-05, ref #920) already landed a base tracker: models + TrackStats with the shared fixtures, ingest filter, location foreground service, Room store with tombstones/crash snapshots, WorkManager sync, MapLibre map, and all three Compose screens.

So instead of a re-port, spikersoft-android PR #4 audits the merged code against the iOS behavioral contract and closes the real gaps:

  • Fixes a real breakage on main: tracker_style.json was loaded from assets/ but that dir is gitignored (Godot sync target) — the map was blank on every fresh clone. Style now committed sha-identical to iOS at assets-tracker/ + assets.srcDir; verified inside the APK.
  • segment on the upload wire (epic #983 contract), null-accuracy fix rejection, 1s HIGH_ACCURACY request with recorder-side sampling, pause stops location collection, iOS-shaped crash recovery (Resume/Discard dialog), stop→save-sheet flow, ON_START foreground sync.
  • Tests: 15/15 green incl. new 7-case recorder test; fixtures eye-checked against iOS (111.195 km etc.).

Kept deliberately from PR #3's architecture (not regressed to iOS file-for-file): Room instead of file-per-route JSON, WorkManager instead of foreground-only sync, MapLibre 13.4.1. Behavioral contract items all hold.

Outstanding: emulator GPX end-to-end pass; ghost racing on Android remains the follow-up after this merges.

Premise correction: this ticket said "Android has NO tracker" — that was read from a **stale local checkout** (8 commits behind). spikersoft-android PR #3 (merged 2026-08-05, ref #920) already landed a base tracker: models + TrackStats with the shared fixtures, ingest filter, location foreground service, Room store with tombstones/crash snapshots, WorkManager sync, MapLibre map, and all three Compose screens. So instead of a re-port, spikersoft-android **PR #4** audits the merged code against the iOS behavioral contract and closes the real gaps: - **Fixes a real breakage on main**: `tracker_style.json` was loaded from `assets/` but that dir is gitignored (Godot sync target) — the map was blank on every fresh clone. Style now committed sha-identical to iOS at `assets-tracker/` + `assets.srcDir`; verified inside the APK. - `segment` on the upload wire (epic #983 contract), null-accuracy fix rejection, 1s HIGH_ACCURACY request with recorder-side sampling, pause stops location collection, iOS-shaped crash recovery (Resume/Discard dialog), stop→save-sheet flow, ON_START foreground sync. - Tests: 15/15 green incl. new 7-case recorder test; fixtures eye-checked against iOS (111.195 km etc.). Kept deliberately from PR #3's architecture (not regressed to iOS file-for-file): Room instead of file-per-route JSON, WorkManager instead of foreground-only sync, MapLibre 13.4.1. Behavioral contract items all hold. Outstanding: emulator GPX end-to-end pass; ghost racing on Android remains the follow-up after this merges.
Author
Owner

Resolved in spikersoft-android PR #4 (merged to main), on top of the base tracker that PR #3 had already landed (see premise-correction comment above). Gap-closing audit vs the iOS behavioral contract: committed the gitignored map style (blank-map fix), segment on the upload wire, ingest parity (null-accuracy rejection, 1s HIGH_ACCURACY request), pause stops location collection, iOS-shaped crash recovery, stop→save-sheet flow, ON_START sync. 15/15 unit tests green with the shared TrackStats fixtures. Android ghost racing remains the epic #983 follow-up; the record-follow UX fix is #988 (PR #5). Closing.

Resolved in spikersoft-android PR #4 (merged to `main`), on top of the base tracker that PR #3 had already landed (see premise-correction comment above). Gap-closing audit vs the iOS behavioral contract: committed the gitignored map style (blank-map fix), `segment` on the upload wire, ingest parity (null-accuracy rejection, 1s HIGH_ACCURACY request), pause stops location collection, iOS-shaped crash recovery, stop→save-sheet flow, ON_START sync. 15/15 unit tests green with the shared TrackStats fixtures. Android ghost racing remains the epic #983 follow-up; the record-follow UX fix is #988 (PR #5). Closing.
Sign in to join this conversation.