Skip to content
All writing
Engineering9 min read

Building an App That Keeps Working When the Signal Drops

Most apps treat no-signal as an error state. For anything used on a shop floor, in a warehouse or out in a field, it is the normal state.

Open a typical app on a bad connection and you get a spinner, then an error, then a retry button. That is a reasonable design for an app used at a desk. It is the wrong design for one used in a basement stockroom, on a factory floor, in a lift, or standing in a field — and a lot of business software is used in exactly those places.

The distinction is not how much offline support you add. It is which side you build from.

Who this is for

Anyone commissioning an app whose users are not sitting still on good Wi-Fi: field staff, delivery teams, retail and hospitality floors, warehouses, clinics, anything agricultural. It is also worth reading if your app already exists and people complain that it "does not work properly" without being able to say exactly when.

Offline-as-a-feature versus offline-first

The usual approach builds the app against the network and adds offline handling afterwards. The app asks the server for data, and if the request fails it shows an error and perhaps serves something stale. Offline becomes an exception branch, and exception branches tend to be the least-tested code in a codebase.

Offline-first inverts it. The app reads from a local database on the device, always. That local copy is what the interface renders — never a network response. Syncing runs alongside, updating the local copy when a connection exists. The network becomes something that improves the data rather than something the screen waits for.

What this actually requires

1. A real local database

Not a cache you clear when convenient — a proper store that survives restarts and is treated as the source the UI reads. On Flutter that is typically Hive, Isar or SQLite. Some backends help here: Firestore ships with offline persistence and handles a good portion of this for you, which is often the fastest sensible route.

2. Writes that queue instead of failing

When someone marks a job done with no signal, that action must be recorded locally, reflected in the interface immediately, and sent later. The queue has to survive the app being closed and the phone being restarted, because it will be.

3. A decision about conflicts

Two people edit the same record offline. Both come back online. Something has to give, and the correct answer is a product decision rather than a technical one: last write wins, merge field by field, or surface it and ask a human. Choosing nothing means choosing last-write-wins by accident and quietly losing someone's work.

4. Honesty in the interface

If a change has not synced yet, say so — quietly, but say it. A pending state is easy enough to live with. Being shown a confirmation for something that silently never happened is not.

How this looked on a real build

Shiftly is shift management for teams, and it pushed hard on exactly this. The insight that shaped it was that the rota is rarely wrong — the version someone is looking at is. Re-sending a spreadsheet creates another version rather than replacing the last one.

So the goal became one propagating source of truth rather than a better way to send a rota around. Hive holds a local copy and Firestore reconciles it, which means the app opens into the schedule rather than a loading state, and stays readable when the signal drops mid-shift. Writes queue and settle when connectivity returns — which matters on a shop floor or a back-of-house network, which is precisely where the app is used.

AgreeCare has a related constraint from the other direction: it streams live sensor readings, and the interface has to stay responsive while values arrive continuously. Keeping state out of the widget tree is what stops a steady data feed turning into a stuttering screen.

Testing it properly

  • Airplane mode is the easy case. Fully off is simple; it is the in-between that breaks things.
  • Test a bad connection, not a dead one. Requests that hang for thirty seconds and then fail expose far more bugs than a clean disconnection.
  • Kill the app mid-sync. Force-quit with items in the queue, reopen, and check nothing was lost or sent twice.
  • Go offline on one device, change the same record on another. This is where your conflict decision either exists or does not.
  • Leave it offline for a day. Queues that work for five minutes sometimes do not work for five hours.

When not to bother

This is real engineering and it is not free. If your app is used at a desk on reliable Wi-Fi, if the data is inherently live and stale values are useless — a payment balance, a live auction — or if it is mostly a browsing experience over a big remote catalogue, then standard caching and a decent error state are the proportionate answer.

The test I would apply: if a user cannot do their job for the next ten minutes because the signal dropped, offline-first is worth it. If they are merely inconvenienced, it probably is not. If you are not sure which describes your situation, that is worth a conversation before the architecture is chosen — it is much cheaper to decide this at the start than to retrofit it. The Flutter app development page covers how I scope that.

Common questions

Does offline-first make the app more expensive to build?
It adds work — the local store, the sync layer, the conflict decision and the testing. Retrofitting it onto a finished app costs considerably more than designing for it at the start, which is the main reason to decide early.
Does Firebase handle this for me?
Firestore includes offline persistence and queues writes, which covers a good portion of the problem and is often the fastest sensible route. It does not decide your conflict strategy or design your pending states — those are still yours.
How much data can the app store locally?
Far more than most business apps need. The practical limit is usually what is sensible to sync and keep current, not what the device can physically hold.
What happens if two people change the same thing offline?
Whatever you decided would happen. That is the point of naming a conflict strategy up front — without one you get last-write-wins by default, and someone quietly loses work without ever being told.
Can a website work offline too?
To a degree, with service workers and local storage, and for simple cases it is genuinely useful. It is less capable and less predictable than a native app doing the same job, particularly on iPhone.

Keep reading

Have an idea you want to build?

Tell me what you are working on and I will come back with a plan, a timeline and a price.