Offline-First Mobile: Lessons from Building a Healthcare App
What happens when your users lose connectivity mid-patient record? How I designed sync queues, conflict resolution, and graceful degradation for a real clinic environment.
The Constraint That Shaped Everything
The clinic operated in a building with dead zones. Exam rooms in the basement had no signal. The WiFi was shared with 12 other tenants. Doctors moved between floors constantly. Any app that required constant connectivity was useless.
This wasn't a nice-to-have feature. Offline-first was the core architectural decision that shaped every layer of the stack.
The Sync Queue Pattern
Every write operation goes through a local sync queue before touching the network. The queue is persisted to device storage, so even a force-quit doesn't lose data.
@action savePatient(data: Patient) {
// 1. Write locally first (instant UI)
this.persistLocal(data);
// 2. Queue for remote sync
this.syncQueue.push({
type: 'UPSERT',
collection: 'patients',
payload: data,
timestamp: Date.now(),
retryCount: 0,
});
// 3. Attempt sync if online
if (this.isOnline) this.flushQueue();
}
The queue processor runs on a background timer. When connectivity returns, it flushes pending operations in order, handling failures with exponential backoff.
Conflict Resolution
The hardest problem: what happens when two devices modify the same patient record while both are offline?
I used a last-write-wins with field-level merging strategy. Instead of treating each record as atomic, the sync system tracks which fields changed:
- Device A updates the phone number while offline
- Device B updates the diagnosis while offline
- When both sync, the system merges non-conflicting fields automatically
- True conflicts (same field, different values) surface to the user with both versions
In healthcare, silently choosing a winner for conflicting data isn't acceptable. When in doubt, show both values and let the doctor decide.
The Status Bar That Changed Everything
The single most impactful UI decision was a persistent sync status bar showing exactly what the user needs to know: "Synced 2 min ago · 3 pending". No ambiguity, no hidden state. Users trusted the app because it was transparent about connectivity.
Key Takeaways
- Design for the worst case first. If your app works offline, it works everywhere.
- Make sync state visible. Users don't mind latency — they mind uncertainty.
- Field-level diffing beats record-level. Most "conflicts" aren't really conflicts.
- Test with airplane mode on. I kept my phone in airplane mode for entire sprints. It changes how you think about every interaction.
- MobX observables + async queues = reactive offline UX. The UI stays live even when the network is dead.