Offline data loading and sync
⚠️ Status: superseded. The mobile consolidation (2026-06) removed the Android Room/offline layer, the
core-datalibrary, the per-feature Hilt modules, and thesync-algorithm-design.mdshared sync stub. This document describes the previous architecture. Post-consolidation, all clients are online-first (network-only). Offline and sync capabilities are being re-evaluated; this doc is retained as a reference for what was previously built and what may be restored.
Background (what was removed)
The Android app previously had:
- Room local databases for tasks, habits, and other feature data (stale-while-revalidate cache)
- Firebase Remote Config for data policy mode (
cache_fallback,force_online,remote_only) StaleWhileRevalidateCoordinatorincore-data— returned cached data immediately, refreshed from network in backgroundBaseRepositorywithdoRequest/doRemoteRequestpatternSyncUtils+ WorkManager workers for batch offline sync of tasks/habitscore-data,core-ui, andtest-coreGradle modules
These were removed as part of the mobile consolidation (branch design-system-foundation) — see tech-debt/migrations.md for details.
Current state (post-consolidation)
| Area | Status |
|---|---|
| Local caching | None. All reads hit the server. |
| Offline support | Not available. No offline queue, no stale cache. |
| Sync | Not available. The shared Ktor SyncService stub exists but is not wired. |
| Data policy | Network-only. DataPolicyMode still exists in shared but is not used by any repository. |
| Desktop | Online-first (unchanged). |
| Wasm | Not a shipped client. |
Future direction
The offline/sync architecture is being re-evaluated. Likely approaches under consideration:
- Re-introduce a shared offline cache using a multiplatform local database (e.g., SQLDelight, or a simple DataStore-backed queue) — this would restore the soft-UX pattern across all platforms, not just Android.
- Keep network-only and document it as a product decision, prioritizing real-time sync and optimistic UI instead.
- Reintroduce sync only (without offline cache) — the
SyncServicestub insharedcould be completed for batch operations.
See the sync-algorithm-design.md for the original design goals and the tech-debt register item TD-08 for current status.
Historical architecture (removed)
Data policy modes
| Mode | Enum | Role |
|---|---|---|
cache_fallback | CacheFallback | Default stale-while-revalidate: local first + background refresh |
force_online | ForceOnline | Prefer freshness; always attempt remote fetch |
remote_only | RemoteOnly | Network-only reads |
Resolution order (Android, removed)
- Firebase Remote Config key
data_policy_mode BuildConfig.DATA_POLICY_MODE_FALLBACKfrom envOTER_DATA_POLICY_MODE- Safe default
cache_fallback
Repository tiers (removed)
| Tier | Meaning | Examples |
|---|---|---|
| A | Room + remote; local-first + background refresh | Tasks, habits |
| B | Partial local cache | Workout, nutrition |
| C | Network-only | Finance, dashboard, auth |
Manual sync contract (removed)
SyncUtils.sync ran WorkManager-triggered batch sync for tasks and habits:
- Load
last_sync_timefrom preferences SyncRepository.getLocalSyncData()— local pending changesSyncRepository.sync()— server processes batch- Apply deltas ordered by action (INSERT, UPDATE, DELETE) with remote-ID mapping
Known limitations
- Not every screen yet exposes refresh state in UI state.
- No per-feature data policy overrides (only global mode).
- Desktop does not run Firebase Remote Config.
- Wasm is not a shipped product client — do not assume Wasm parity with Android.
Related code (preserved references)
shared/.../policy/DataPolicyMode.kt,DataPolicyProvider.kt— still in shared moduleshared/.../services/sync/SyncService.kt— Ktor stub (unwired)composeApp/.../FirebaseDataPolicyProvider.kt,EnvDataPolicyProvider.kt— still present for potential re-use
Manual sync contract (tasks / habits)
SyncUtils.sync (Android) runs only when NetworkHelper.isNetworkAvailable():
- Load
last_sync_timefrom preferences. SyncRepository.getLocalSyncData(lastSyncTimestamp)— local pending changes.SyncRepository.sync(localSync)— server processes batch.- On success: apply tasks and habits deltas via
SyncHelper.syncData(ordered by action: INSERT, UPDATE, DELETE), then maptasksSynced/habitsSyncedremote IDs back onto local rows, thenpreferences.saveLastSyncTime(lastTimeStamp).
Ordering of operations matters for referential consistency; keep INSERT before UPDATE/DELETE per entity stream when extending.
Background sync (WorkManager)
Periodic and boot-driven scheduling lives under composeApp (SyncDataWorkerImpl calls SyncUtils.sync; WorkManagerUtilsImpl enqueues work; BootReceiver can trigger runWorkManagerTasks). OterApp may comment out automatic enqueue on cold start — check the current line before assuming sync runs at launch.
Write path and local consistency (audit)
| Area | Behavior |
|---|---|
| Tasks / habits | Mutations use doRequest with remote + local paths; list consistency after remote success relies on repository implementation + sync for pending offline work. |
| SWR list refresh | Successful background refresh calls mergeRemoteSnapshotWithoutHistory (tasks/habits) to avoid deleting unsynced local rows. |
| Workout | Mutations use doRemoteRequest where local mirror is not implemented — UI should refetch or rely on next navigation. |
| Finance | Remote-first writes; no Room mirror in tier C — presentation handles errors softly. |
| Recipes / nutrition | Follow doRequest + preferences; confirm screen-level refetch after mutations where needed. |
Gaps should be fixed in-repo or called out in feature guides when product requires strong offline write parity.
Supported clients
- Android: Remote Config +
BuildConfig/ env fallback; Room;SyncUtils+ WorkManager triggers as wired in app module. - Compose Desktop: Ktor online-first;
EnvDataPolicyProvideronly; no Room sync stack equivalent to Android. - Wasm: Not in use for this product — no client work or parity guarantees.
Known limitations
- Not every screen yet exposes refresh state in UI state; tasks/habits use domain events for refresh failures.
- Per-feature RC overrides (e.g.
data_policy_finance) are not implemented; only globaldata_policy_mode. - Desktop does not run Firebase Remote Config.
Related code
shared/.../policy/DataPolicyMode.kt,DataPolicyProvider.ktcore-data/.../StaleWhileRevalidateCoordinator.kt,BaseRepository.ktcomposeApp/.../FirebaseDataPolicyProvider.kt,EnvDataPolicyProvider.ktcomposeApp/.../SyncUtils.kt