AriesDevs

Engineering

Offline-first mobile apps: sync patterns that hold up

A queue, an idempotency key and a clear conflict rule get you most of the way. The rest is deciding what the user sees while the network is away.

Blue 3D smartphone showing a chat bubble

AriesDevs engineering team Engineering notes

4 min read

Field teams work in basements, lifts, warehouses and rural roads. An app that shows a spinner whenever the signal drops is not a field app. A small set of patterns makes mobile apps behave well on poor networks without turning the codebase into a distributed-systems research project.

Treat the local database as the source of truth for the UI

The screen should read from a local database such as SQLite, never directly from the network response. Network calls update the database, and the database updates the screen. This single rule removes whole categories of bugs: loading states that never resolve, half-rendered lists and stale data after a retry.

Writes follow the same path. When a user taps "Complete inspection", we write the change locally, update the screen immediately and add an entry to an outbox table. A background worker sends outbox entries when connectivity returns.

Make every write idempotent

Mobile networks fail in the worst place: after the server has processed the request but before the phone hears about it. The app retries, and without protection you create the inspection twice. Every outbox entry therefore carries a client-generated unique ID, and the server stores it and ignores repeats.

typescript
interface OutboxEntry {
  id: string            // UUID created on the device
  entity: 'inspection'
  entityId: string
  operation: 'update'
  payload: Record<string, unknown>
  baseVersion: number   // version the user was looking at
  createdAt: string
  attempts: number
}
An outbox entry. The id makes retries safe; baseVersion makes conflicts detectable.

Retries use exponential backoff with jitter and a cap, so a thousand devices coming back online after an outage do not flatten the server together.

Decide the conflict rule before you write the code

Conflicts happen when two people change the same record while one is offline. There is no universal answer, so agree one rule per kind of data with the business.

  • Last write wins is fine for low-stakes fields such as a note or a preferred contact time.
  • Field-level merge works for records with many independent fields: if you edited the address and I edited the phone number, keep both.
  • Server decides, user is told is right for things like stock counts and status changes. The app shows what happened and lets the user re-apply.
  • Manual resolution is for the rare, high-value cases, such as two engineers signing off the same job with different results.

The baseVersion in the outbox entry lets the server see that an edit was made against an old copy and apply the agreed rule, rather than silently overwriting.

Be honest in the interface

Users do not mind being offline. They mind not knowing whether their work was saved. We show a small, consistent state on each item: saved on this device, syncing, synced, or needs attention. A global banner says how many changes are waiting. When something fails permanently, such as a rejected photo upload, the app says what it is and offers a retry or a way to export it.

Test with a hostile network

Most sync bugs only appear under bad conditions. Our test plan includes airplane mode mid-upload, a response that arrives after a timeout, two devices editing the same record, an app kill during sync and a clock that is wrong by a day. Network link conditioners and a scripted server that drops every third request find more problems in an hour than a week of testing on office wifi.

Offline-first costs more up front, because you design the data model carefully and test the awkward cases. But it removes the biggest source of support tickets for field apps, and users notice when an app simply works wherever they are.

Talk to an engineer

Have a project like this?
Let us talk it through.

Bring the problem, not the solution. A senior engineer will give you an honest view of the options, the trade-offs and a realistic cost range.