AriesDevs engineering team Engineering notes
4 min read
Field teams work in basements, lifts, warehouses and rural roads. An app that shows a spinner whenever the signal drops is not a field app. A small set of patterns makes mobile apps behave well on poor networks without turning the codebase into a distributed-systems research project.
Treat the local database as the source of truth for the UI
The screen should read from a local database such as SQLite, never directly from the network response. Network calls update the database, and the database updates the screen. This single rule removes whole categories of bugs: loading states that never resolve, half-rendered lists and stale data after a retry.
Writes follow the same path. When a user taps "Complete inspection", we write the change locally, update the screen immediately and add an entry to an outbox table. A background worker sends outbox entries when connectivity returns.
Make every write idempotent
Mobile networks fail in the worst place: after the server has processed the request but before the phone hears about it. The app retries, and without protection you create the inspection twice. Every outbox entry therefore carries a client-generated unique ID, and the server stores it and ignores repeats.
interface OutboxEntry {
id: string // UUID created on the device
entity: 'inspection'
entityId: string
operation: 'update'
payload: Record<string, unknown>
baseVersion: number // version the user was looking at
createdAt: string
attempts: number
} Retries use exponential backoff with jitter and a cap, so a thousand devices coming back online after an outage do not flatten the server together.
Decide the conflict rule before you write the code
Conflicts happen when two people change the same record while one is offline. There is no universal answer, so agree one rule per kind of data with the business.
- Last write wins is fine for low-stakes fields such as a note or a preferred contact time.
- Field-level merge works for records with many independent fields: if you edited the address and I edited the phone number, keep both.
- Server decides, user is told is right for things like stock counts and status changes. The app shows what happened and lets the user re-apply.
- Manual resolution is for the rare, high-value cases, such as two engineers signing off the same job with different results.
The baseVersion in the outbox entry lets the server see that an edit was made against an old copy and apply the agreed rule, rather than silently overwriting.
Be honest in the interface
Users do not mind being offline. They mind not knowing whether their work was saved. We show a small, consistent state on each item: saved on this device, syncing, synced, or needs attention. A global banner says how many changes are waiting. When something fails permanently, such as a rejected photo upload, the app says what it is and offers a retry or a way to export it.
Test with a hostile network
Most sync bugs only appear under bad conditions. Our test plan includes airplane mode mid-upload, a response that arrives after a timeout, two devices editing the same record, an app kill during sync and a clock that is wrong by a day. Network link conditioners and a scripted server that drops every third request find more problems in an hour than a week of testing on office wifi.
Offline-first costs more up front, because you design the data model carefully and test the awkward cases. But it removes the biggest source of support tickets for field apps, and users notice when an app simply works wherever they are.