A familiar story with field apps: the first version works perfectly in the office and falls apart in a plant room, a basement or a warehouse — which is where the work actually happens.
The mistake in both cases was the same: treating offline as an error state rather than the normal state.
The mental model that fails
Most apps are built assuming a request will succeed. When it does not, you show a spinner, then an error, then a retry button. This works fine for a social app on the underground for three minutes. It fails completely for an engineer who spends four hours in a basement.
The mental model that works
The local database is the source of truth for the user's session. Every action writes locally and returns immediately. A background process syncs to the server when it can. The user never waits for the network, and never sees an error because of it.
This changes almost every decision in the app.
What it actually requires
A real local database. SQLite or WatermelonDB, not AsyncStorage with JSON blobs. You need queries, indexes and transactions.
Client-generated identifiers. Records need IDs before they reach the server, so use UUIDs. Never wait for a server-assigned auto-increment.
An outbox. Every mutation is appended to a queue with an idempotency key. The sync process drains the queue in order and marks entries complete. If the app dies mid-sync, the queue survives.
Idempotent server endpoints. The client will retry. If a retry creates a second record, you have built a duplicate-generation machine. Every mutating endpoint takes an idempotency key and returns the original result on a repeat.
A conflict policy decided in advance. Two users edited the same job sheet. What happens? Last-write-wins is fine for some fields and catastrophic for others. Decide per field, write it down, and make the resolution visible to the user rather than silent.
Honest sync status. Not a spinner. Tell the user: 12 items pending, last synced 40 minutes ago. Field workers are entirely comfortable with this if you are straightforward about it. What they cannot tolerate is uncertainty about whether their work was saved.
The parts people forget
Reference data. The product catalogue, the price list, the customer list. These need to be on the device, versioned, and updated incrementally rather than re-downloaded whole.
Authentication. Tokens expire. If a token expires while offline, the app must not log the user out and lose their queued work. Keep a longer-lived refresh path and treat auth failure as a sync deferral, not a session termination.
Sequence numbers. For anything requiring a legally continuous sequence — invoices, for example — allocate a reserved range per device. Never generate them client-side without reservation.
Storage limits. Photos will fill a device. Compress on capture, upload opportunistically, and delete locally after confirmed upload.
Testing it honestly
Airplane mode is not a test. Real conditions are worse: connections that establish then time out, partial responses, DNS that resolves to nothing. Test with a proxy that introduces latency and packet loss, and run at least one full day of realistic use with the network toggling.
The result
The payoff is concrete: when a job sheet can be completed on site instead of remembered later, less work goes unbilled. That is not a network feature. It is what happens when the architecture matches the environment.