MarcoRestif
All articles
· 2 min read

Offline-first mobile apps: what actually breaks in the field

Lessons from shipping an inspection app used in basements, tunnels and rural sites. Sync conflicts, photo uploads and the UX of not knowing whether you are online.

"It should work offline" is one line in a requirements document and about a third of the engineering effort. Here is what went wrong on a field inspection app before it went right.

Connectivity is not a boolean

Devices report "online" while sitting on a cellular connection that cannot complete a 2 MB upload. Treating connectivity as true | false produced spinners that never ended.

What worked:

  • Treat every network call as optimistic. Write locally first, queue the request, show the result immediately.
  • Measure recent success, not the OS flag. If the last three uploads failed, you are offline for UX purposes.
  • Show the queue. A small "3 reports waiting to sync" badge removed most support tickets.

Conflicts are rarer than you fear, but ugly when they happen

Two technicians editing the same inspection is rare. One technician editing the same inspection on two devices is not. We settled on:

  1. Last write wins for scalar fields.
  2. Append-only for lists (photos, notes), merged by id.
  3. A visible "updated on another device" banner instead of silent overwrites.

Do not build a general CRDT engine for a checklist app. Pick rules per field type and write them down.

Photos are the real payload

Text syncs in milliseconds. Photos are where the app lives or dies.

upload_queue.dart
Future<void> drain() async {
  for (final job in await store.pending()) {
    try {
      await uploader.send(job, compress: true);
      await store.markDone(job.id);
    } on TransientError {
      break; // keep order, retry later with backoff
    }
  }
}

Compress on device, upload in order, and never block the UI on it.

The checklist I now use

  • Local database is the source of truth; the server is a replica.
  • Every mutation has an id and is idempotent on the server.
  • The sync status is visible, honest and tappable.
  • Test with airplane mode toggled mid-upload, every release.
MR

Marco Restif

Software engineer · AI & full-stack. Writing about what I build.

Read next

Working on something similar?

I take on a few projects at a time. Get in touch and we can see if it is a fit.