Mobile
Lesson 2 of 8About 2 min readSuggest an edit

Offline-first data

Phones lose signal in lifts, trains and basements, and flaky networks are worse than none: requests hang for 30 seconds and then fail. An offline-first app treats the network as an optimisation, not a requirement.

The local database is the source of truth

The UI reads from and writes to a database on the device, such as SQLite (directly, or through Room on Android and Core Data or SwiftData on iOS). A separate sync layer moves changes between the device and the server in the background.

UI  <->  local database  <->  sync engine  <->  server API

This gives instant screens, because nothing waits on the network, and it keeps working without signal. It is also simpler to reason about: the UI observes local data and re-renders when it changes, whoever changed it.

Queue writes as operations

When the user acts, write the change locally at once, then record an operation in an outbox table:

outbox: id | type        | payload                        | attempts
        7  | add_comment | {"post": 42, "text": "Nice!"}  | 0

The sync engine sends pending operations in order when a connection is available, retries with exponential backoff, and deletes each one once the server acknowledges it. Give every operation a client-generated id and have the server treat it as an idempotency key, so a retry after a lost response doesn’t create a duplicate comment.

Pulling changes

To fetch what changed elsewhere, ask the server for changes since the last sync, using a cursor or a server-side version number rather than the device clock, which may be wrong. Deletions need to be transmitted too, usually as tombstones: records marked deleted instead of removed, so other devices learn about them.

Conflicts

Two devices edit the same record while offline. When both sync, you must decide what wins:

  • Last write wins: simple, but silently loses one edit. Acceptable for low-value fields such as a profile photo.
  • Field-level merge: if one device changed the title and the other the description, keep both.
  • Ask the user: show both versions. Best for important content, but interruptive.
  • CRDTs (conflict-free replicated data types): data structures designed to merge automatically, such as counters and collaborative text. Powerful, and more complex to adopt.

Choose per field, based on what losing an edit would cost the user.

Show sync state honestly

Users should know whether their work is safe:

  • mark items that haven’t synced yet, for example with a small “waiting to upload” icon;
  • show when a sync failed and what to do about it;
  • never show a success message for something only saved locally, if the user might assume others can already see it.

Test the unhappy paths

Test in airplane mode and with network throttling, kill the app mid-sync, change the device clock, and edit the same record on two devices. Offline bugs rarely show up on a fast office Wi-Fi.

Next: Mobile performance

The main thread and frame budget, smooth lists, images, app start-up and battery.