Offline-First Sync Engines
10 January 2025
Room/SQLite, a clear sync state machine, and conflict resolution rules that don’t surprise users.
TL;DR
Use a local DB (Room/SQLite) as the source of truth for reads; one sync state machine (idle / syncing / error); conflict resolution rules documented and tested; backoff and idempotent writes for pushes.
Architecture
Local DB (Room/SQLite) is the source of truth for reads; UI reads from DB only. Writes go to a queue or directly to DB with a “dirty” flag. A sync worker runs when online: pull latest from server, merge (with defined conflict rules), then push dirty records with idempotency keys. Sync state (idle, syncing, error) is exposed so the UI can show a banner or disable actions. Conflict resolution: for user-generated content, prefer server or merge; for config, last-write-wins is often fine.
Failure modes
- Stale data shown as truth after long offline.
- Sync loop: client and server keep overwriting each other.
- Unbounded growth: sync table or queue never pruned.
- Corruption after app update or schema migration.
Testing checklist
- Unit tests for sync state machine transitions.
- Instrumented tests with fake server: go offline, write, go online, confirm sync.
- Conflict scenarios: same record edited offline on two devices, then sync.
- Chaos test: kill app during sync; restart and confirm no duplicate or lost data.
What I'd do differently next time
I’d version the sync protocol and support at least one prior version so we can roll out server changes without breaking older app versions in the wild.