Offline Mode and Synchronization in Mobile Apps: How to Get It Right

Why an app needs to work offline at all
In a perfect world every user always has stable internet. In reality a worker walks into a warehouse basement, a sales agent drives between districts, an installer works in a new building with no coverage, and a courier descends into an underground parking lot. If the app turns into a blank screen with a spinning indicator at those moments, the business loses data and people's trust.
Offline mode isn't about "caching a couple of screens." It's about letting the user keep working as if there's no connection at all: creating records, filling out forms, marking tasks done. The app itself then quietly and reliably delivers everything to the server the moment the network returns. For B2B scenarios this is often not a nice-to-have but a hard requirement: without it, the tool simply won't be used in the field.
In Uzbekistan's conditions this is especially apparent. 4G coverage in Tashkent is good, but beyond the big cities connectivity is patchy: between Samarkand and the villages, on highways, in the mountainous areas of Kashkadarya or Surkhandarya, the internet comes and goes. An app that assumes constant connectivity is useless exactly where it's needed most.
What "offline-first" really means
There are two fundamentally different architectures. In the first, the app is a thin client: every action flies straight to the server, and nothing happens without a response. In the second, offline-first, the source of truth for the interface is a local database on the device (SQLite, Realm, WatermelonDB and the like), and the server synchronizes with it in the background.
The key choice at project start: whether to build the app offline-first. This decision shapes the entire architecture — data structures, API, sync model. It's cheap to design in at the start and very expensive to retrofit later. If even part of your users work in the field, commit to offline-first from day one.
In the offline-first model the interface is always responsive: data is read and written locally instantly, without waiting on the network. This brings a pleasant side effect too — the app feels fast even on good internet, because it doesn't wait for a round-trip to the server on every tap.
The operation queue: the heart of synchronization
When a user does something offline, you can't just "remember the screen." You have to store the intent — the operation: "create customer," "change request status to done," "attach photo to order." These operations go into a local queue and are marked as unsynchronized.
As soon as the network appears, a background process starts sending operations in order. Several things matter here:
- Idempotency. Each operation gets a unique identifier (UUID) generated on the device. If the server response is lost but the operation actually went through, a retry won't create a duplicate — the server recognizes the operation by its ID.
- Order and dependencies. You can't send "add item to order" before the order itself is created. The queue must respect cause-and-effect relationships.
- Retries with backoff. On a network failure the operation isn't discarded but deferred with an increasing pause, so it doesn't hammer the server pointlessly.
- Restart resilience. The queue lives in persistent storage. If the user closes the app or the phone dies, sync resumes from the same point once it's back on.
A common mistake: keeping the queue only in RAM. The app gets killed by the system under memory pressure, the phone reboots — and unsaved operations vanish along with half a day of the worker's effort. The queue must be persistent from day one.
Conflicts: what to do when edits diverge
The hardest part of synchronization is conflicts. Two employees edited the same request offline, then both came back online. Whose changes win? There's no universal right answer, only strategies, and you must choose them deliberately based on business logic.
Last-write-wins: the record with the later timestamp wins. Simple to implement, but silently overwrites someone else's work. Fine for non-critical fields like notes.
Field-level merge: if one person changed the address and another the phone number, we combine both changes. More complex, but preserves the maximum amount of data. Suitable for customer and order records.
Manual resolution: on a genuine conflict we show the user both versions and ask them to choose. More costly in UX, but mandatory where the cost of error is high — finance, healthcare, contracts.
In practice the good approach is to combine: merge most fields automatically, and escalate the truly contested cases to a human. It's important to version records (for example via a revision number or vector clocks) so the server understands which version of the data an offline edit was based on.
Real-world use cases
Where offline mode pays off immediately:
- Field service crews. Installation, repair, equipment maintenance. The technician fills out a work completion report, attaches photos, captures the customer's signature — all without a network in a basement or on a construction site, syncing on the way back.
- Sales agents and merchandisers. Visiting outlets, taking orders, checking shelf stock. The route passes through dead zones, but the work doesn't stop for a second.
- Logistics and delivery. The courier marks statuses, records the recipient, scans barcodes in underground parking lots and thick-walled warehouses.
- Field surveys and inspections. Agricultural assessments, site checks, data collection in the regions — where stable internet never was and never will be.
- Warehouse operations. Inventory counts in metal hangars where Wi-Fi and cellular barely penetrate.
How we implement it
Technically, a modern offline stack looks like this: a local database on the device as the single source of truth for the UI; a repository layer that hides from the interface where data came from; a changelog of operations with their statuses; a background synchronizer that triggers on the network-available event and on a schedule.
On the server side you need an API designed for synchronization: endpoints accept batches of operations with client IDs, return a delta of changes since the last sync (by cursor or timestamp), and report conflicts correctly. Pulling all data in full on every launch is unacceptable — you should sync only what changed.
Separate attention goes to state indication. The user must see that data hasn't been sent yet ("pending sync"), that syncing is in progress, and that everything has reached the server. Without this feedback, people don't trust the app and start duplicating work on paper.
Conclusion
Offline mode and synchronization are not an extra feature you can "bolt on later" but an architectural decision that affects the whole project. A well-designed operation queue, a thought-out conflict strategy, and honest state indication turn a mobile app into a reliable working tool that doesn't fail at the most critical moment. Given the uneven coverage across Uzbekistan's regions, this often decides whether your product gets used at all. If you're planning an app for field teams, logistics, or fieldwork — let's discuss your project: at OneDev we'll help lay an offline-first architecture from the very start, so you don't have to rebuild everything from scratch later.
Does every app need to be offline-first?
How is offline mode different from regular caching?
What happens to data if the phone shuts down while working offline?
How do you avoid duplicates when operations are resent?
How do you decide whose changes matter more in a conflict?
How much does offline mode increase development cost?
Need a similar system or want to discuss your project?
Describe the task — we will propose architecture, technical approach and a work plan. A short call is usually enough to get started.
Discuss project