Most mobile apps are built on an office wifi connection and tested on the same. Then they ship to someone in a basement, a lift, a rural road, or a building with thick walls, and the app spins forever because it never considered that the network might simply not be there.
Offline is not an edge case. For a large share of real usage it is a normal condition, and designing for it changes the architecture rather than adding a feature.
The distinction that matters
There are two very different things people mean by offline support.
Read offline is showing content the user already fetched. Cache the response, show it, mark it as possibly stale. This is straightforward and most teams get here.
Write offline is letting someone create or change something with no connection, and reconciling it later. This is the hard one, and it is where the architecture decision lives.
If your app only reads, a cache is enough. If people need to do something in the field, you need the second kind, and you cannot bolt it on later without touching everything — an architectural commitment on the same level as choosing Flutter or native.
Why retrofitting costs so much
An online-first app treats the server as the source of truth. Every screen asks the server, gets an answer, renders it. State lives remotely, and the app is a window.
An offline-capable app treats the local database as the source of truth. The app reads and writes locally and always renders instantly, while a separate process reconciles with the server whenever it can.
That is not a feature difference, it is an inversion. Every screen, every write path, and every piece of error handling assumes one model or the other. Converting means rewriting all of them, which is why “we’ll add offline in v2” usually means “we will rebuild this.”
The three problems you actually have to solve
Identity before the server has seen it. A record created offline has no server ID. Generate one locally — a UUID — and treat it as authoritative. If you wait for the server to assign an ID, nothing created offline can be referenced by anything else, which breaks the moment someone adds a photo to it.
Ordering. Actions queued offline must replay in the order they happened, and they must be safe to replay twice, because you will not always know whether the first attempt landed. Make every operation idempotent — the same request applied twice produces the same result as once.
Conflicts. Two people edited the same record while both were offline. Someone has to decide. “Last write wins” is a decision, not an accident, and it is sometimes correct — but for anything a person typed, it means silently destroying their work. Notes, narratives and free text usually deserve keeping both and asking.
What to tell the user
Silence is the failure. An app that quietly queues everything and shows nothing is indistinguishable from an app that lost your work.
Three states, visible: saved on this device, sending, confirmed by the server. People trust a system that admits it is waiting far more than one that pretends everything is fine.
Never show a success tick for something that has only been written locally. That tick is a promise, and you cannot keep it yet.
When you can skip all this
If the app is only usable with connectivity anyway — a live video call, a real-time dashboard — then offline writes are meaningless and you should not build them.
Be honest about which one you have — it is exactly the sort of thing a discovery call should settle before anyone quotes. The expensive mistake is not skipping offline support. It is assuming you will never need it and discovering in month eight that half your users work somewhere with no signal.
We build mobile apps for people who work away from a desk — field staff, drivers, inspectors. Tell us where yours gets used.