Skip to content

feat(mobile measurements upload): MobileUploadLoop + platform upload backstops - #24

Merged
xameYeoh merged 8 commits into
mainfrom
feat/mobile-meas-upload-workers
Oct 20, 2026
Merged

xameYeoh merged 8 commits into
mainfrom
feat/mobile-meas-upload-workers

Conversation

@xameYeoh

Copy link
Copy Markdown
Collaborator

Summary

Keeps mobile sessions uploaded automatically while the process lives, and hands leftover work to a platform scheduler when the app goes to background.

MobileUploadLoop (commonMain)

A reactive loop driven by local DB state, not events. It observes two SQLDelight queries:

  • selectSessionsWithPending — mobile sessions with rows in pending_uploads
  • selectAwaitingFinish — stopped mobile sessions (end_time set) not yet is_finished_on_server

Timing rules:

  • Pending measurements wait up to flushInterval (60 s) to batch — fewer radio wakeups.
  • A session awaiting finish is uploaded immediately (also cuts the batching wait short).
  • A failed pass backs off exponentially (30 s → 10 min cap), reset on success. One failing session doesn't block the others.
  • After a successful pass it waits for the queries to emit a fresh value instead of re-draining a stale snapshot.

drainOnce() runs a single pass with no batching wait, for the platform backstops. It shares a Mutex with the loop, so the two never upload side by side.

MobileSessionUpload now implements a new SessionUploader fun interface, so the loop can be tested against a fake.

Platform backstops (UploadBackstop.schedule())

  • Android: WorkManagerUploadBackstop: unique one-off work ("mobile-upload", KEEP), NetworkType.CONNECTED, WorkManager backoff via Result.retry(). Survives process death and reboot. Triggered from ProcessLifecycleOwner ON_STOP in AircastingApp. Replaces the legacy ConnectivityReceiver.
  • iOS: IosUploadBackstop: one drainOnce() inside beginBackgroundTask (~30 s), ended synchronously in the expiration handler. Triggered from scenePhase == .background in iOSApp.swift. Known limit: after a force-quit, pending uploads wait for the next launch. They are delayed but never lost, since they stay in SQLite.

Wiring

  • New uploadModule (Koin) registers MobileMeasurementUploader, MobileSessionUpload (as SessionUploader) and MobileUploadLoop. initKoin starts the loop for the life of the process.
  • Platform modules bind UploadBackstop. iOS exposes scheduleUploadBackstop() from KoinIos.kt.
  • Adds androidx.lifecycle:lifecycle-process.

Fix

  • AircastingJson: explicitNulls = false, so null fields are omitted from request bodies. Non-null defaults (e.g. contribute = true) still go out. Test added in MobileSessionUploadTest.

Docs

  • docs/API_V3.md upload section updated: flushNow() dropped, replaced by drainOnce() backstops; Android/iOS hooks documented.

Tests

  • MobileUploadLoopTest: batching window, immediate upload for awaiting-finish sessions, backoff doubling/cap/reset, partial failures, stale-snapshot guard, drainOnce behavior.
  • SessionRepositoryTest / UploadOutboxTest: the two work queries (mobile-only, distinct, finished-state filtering).
  • MobileSessionUploadTest: null-field serialization.

Test plan

  • ./gradlew :shared:allTests green
  • Android: record a mobile session, background the app with network off, re-enable network, and confirm the mobile-upload work runs and the session is finished on the server
  • iOS: background the app with pending uploads and confirm the drain runs in the background task

🤖 Generated with Claude Code

…upload backstops

- uploadModule registers MobileMeasurementUploader, MobileSessionUpload
  (as SessionUploader) and MobileUploadLoop; initKoin starts the loop
  for the life of the process.
- UploadBackstop: WorkManager one-off unique work with a network
  constraint on Android, a beginBackgroundTask drain on iOS. Scheduled
  on ProcessLifecycleOwner ON_STOP / scenePhase .background.
Add unit test in MobileSessionUploadTest to verify stream serialization only sends `sensor_name` and `unit_symbol`.
@xameYeoh
xameYeoh merged commit 6d29ecf into main Oct 20, 2026
2 checks passed
@xameYeoh
xameYeoh deleted the feat/mobile-meas-upload-workers branch October 20, 2026 08:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant