Sigi Technologies has shipped four products used away from a desk: an agritech marketplace for farmers, a warehouse scanning app, and the Antrak and Bix Delivery courier platforms. Exactly one is offline-first. That ratio is the useful part of this guide: offline support is a data-model decision, not a quality bar every app should clear. Scope for work of this shape sits with Sigi’s mobile app development service.
What is an offline-first mobile app?
An offline-first mobile app performs all, or a critical subset of, its core functionality without access to the internet. Google’s Android architecture guidance puts the rule in one line: the local data source is “the canonical source of truth for the app” and “should be the exclusive source of any data that higher layers of the app read.”
The idea predates smartphones. Kistler and Satyanarayanan described disconnected operation in the Coda File System as “a mode of operation that enables a client to continue accessing critical data during temporary failures of a shared data repository,” and found that caching “can also be exploited to improve availability.”
- An offline-first mobile app reads and writes to a local database first and treats the network as a background sync channel, so the interface never waits on a request to paint.
- Android’s architecture guidance sets the floor at reads: at a minimum, an offline-first app must be able to perform reads without network access.
- Offline writes need a durable queue plus an idempotency key per operation, because a retried request the server already applied will otherwise create a second record.
- Last write wins is cheap and lossy; conflict-free replicated data types converge on their own but only fit data whose merge rule is commutative.
- A study of 50 open-source Android apps in Empirical Software Engineering found 304 connectivity-handling issues, six per app on average, most of them messages giving the user wrong information about connection state.
Offline-first, local-first or an offline mode: what is the difference?
The three terms describe different commitments. An offline mode is a fallback: the app is a network client that degrades when the connection drops. Offline-first inverts that and makes the local copy primary. Local-first goes further. In their 2019 essay for Ink and Switch, Kleppmann, Wiggins, van Hardenberg and McGranaghan set out seven ideals for local-first software, among them “the network is optional” and “you retain ultimate ownership and control,” treating the user’s own device rather than a server as holding the primary copy.
For most commercial products the honest target is offline-first, not local-first. A courier platform still needs a server that is authoritative about who is assigned to a job, because the job is not the driver’s private property.
Which apps genuinely need offline support, and which is gold-plating?
This is the question most guides skip, and it has a test. Offline support pays when the action is a fact the user owns and can assert alone: a draft, an observation, a photograph, a count of what is in front of them. It costs more than it returns when validity depends on shared state only the server can arbitrate, such as claiming the last slot, taking a job another driver may already have, or charging a card. The three questions below are Sigi engineering judgement drawn from the four builds above.
- Can the action be true on its own? A count, a photo and a drafted message are facts the user owns. An accepted delivery job is a claim on shared state, and only the server can settle it.
- What does being wrong for an hour cost? A stale price list is an inconvenience. A stale availability calendar that lets two people book the same machine is a lost customer.
- Where is the user standing? Field conditions are a reason to buffer the request, not to make the client authoritative. Buffering is cheap. Moving the decision to the device is not.
Those questions explain Sigi’s own split. The agritech marketplace is offline-first because its users work in fields and barns where mobile signal drops, and the case study is direct about the consequence: a network-dependent app would fail at the exact moment a booking needed confirming.
The other three turn on contested shared state. On Bix Delivery, every delivery is a state machine on the backend and the apps only render the state the server reports, so a customer and a driver never see two versions of one job. Antrak needed that consistency across three surfaces at once, with no gap where a parcel sat in limbo. The 3DLogistiX handset app writes a barcode scan through to the backend so it appears on dashboards and in the 3D warehouse view in the same session. No offline queue is described in that build, and this guide does not invent one.
Where should offline data live on the device?
Offline-first architecture starts with a real database on the device, and SQLite is the default. SQLite’s own guidance says the engine “thrives at the edge of the network, fending for itself while providing fast and reliable data services to applications that would otherwise have dodgy connectivity.” Cache the working set a person needs for one shift or trip, not the whole catalog, and set an eviction rule before release. The layer on top differs by platform.
- Android: Google recommends Room over the SQLite application programming interfaces directly. Room “provides an abstraction layer over SQLite,” with compile-time query verification and defined migration paths.
- iOS: Core Data is Apple’s persistence framework, documented for saving permanent data for offline use and caching temporary data, with model versioning and migration included.
- Flutter: the official persistence cookbook names the sqflite package and advises a database over a file or key-value store for apps that persist and query large amounts of data on the device.
- Key-value stores: preferences belong in SharedPreferences, DataStore or UserDefaults. A queue of pending writes does not, because it needs transactions and a unique constraint.
How do you queue offline writes without losing or duplicating them?
Data synchronization is the step that makes the local copy and the server agree once a connection returns. Its outgoing half is a write queue, often called an outbox: a local table holding every change the server has not confirmed. Android’s offline-first guidance says to insert the object into a queue, then “drain the queue with exponential backoff” once the app is online. Five rules keep that queue safe.
- Write to the local database and to the outbox in one transaction, so the screen and the queue can never disagree about what the user did.
- Give every queued operation a client-generated idempotency key. Stripe’s API documentation describes the effect: the server saves “the resulting status code and body of the first request made for any given idempotency key,” and later requests with that key return the same result instead of creating a second object.
- Preserve order within an entity, not across the app, so a slow photo upload cannot block an unrelated status change.
- Retry with exponential backoff and a ceiling. An operation the server rejects as invalid moves to a dead-letter state a human can inspect.
- Treat a connected network interface as a hint, not proof. A device can sit behind a captive portal with no route out, so confirm reachability against your API before draining.
What schedules the sync on Android and iOS?
On Android, WorkManager is the documented tool. It is intended for work “required to run reliably even if the user navigates off a screen, the app exits, or the device restarts,” and periodic data sync is a listed example. Scheduled work is stored in an internally managed SQLite database and rescheduled across reboots, and constraints let a job wait for an unmetered network. Google warns that a background task running longer than 10 minutes is highly likely to be interrupted, so a large backlog drains in chunks.
On iOS the equivalent is the Background Tasks framework, which lets an app “keep your app content up to date and run tasks requiring minutes to complete even if your app is in the background.” A BGAppRefreshTaskRequest is a short refresh; a BGProcessingTaskRequest covers work taking minutes and can declare that it requires network connectivity or external power. The system decides when those run, so the app must stay correct if sync happens hours later, and should also drain on returning to the foreground.
How do you resolve conflicts when two devices change the same record?
A conflict is two changes to the same data made from incompatible views of it. Android’s data-layer guidance is candid that this is the hard part: a conflict must be resolved before synchronization can happen, resolution “often requires versioning,” and versioning data for conflict resolution is nontrivial. Choose a strategy per data type.
- Server authority: the device sends an intent and the server decides. Correct for anything scarce or financial: bookings, job assignment, stock allocation, payments.
- Last write wins: devices attach timestamp metadata and the server discards writes older than its current state. Cheap, and it silently drops one person’s edit, so keep it for fields where that is acceptable.
- Field-level merge: two people editing different fields of one record should both succeed, which needs a version per field rather than one per row.
- Conflict-free replicated data types: Shapiro, Preguiça, Baquero and Zawirski defined CRDTs as shared data types designed so replicas “converge without foreground synchronisation.” They suit collaborative text, sets and counters, and they do not enforce a constraint such as one booking per machine per window.
The rule: conflict-free replicated data types where two truths can both be kept, server authority where only one can. Most field applications need both, and the line between them is the most important decision in the design. It also decides whether a hosted sync engine fits. A sync engine ships the queue, the transport and a default merge rule, which saves months when the data is mostly per-user, but it will not enforce your business constraints.
What should the user see while the app is offline?
Optimistic UI, short for optimistic user interface, means showing the result of an action immediately and reconciling later. It makes an offline-first mobile app feel fast, and it is where trust is lost, because the app promised something the server has not agreed to. Escobar-Velásquez and colleagues inspected 971 scenarios across 50 open-source Android apps for Empirical Software Engineering and found 304 connectivity issues, six per app on average. Most came from messages giving the user wrong information about connectivity status, or from delegating the connectivity check to an external library.
- Show state per item, not per app. A pending badge on the unsent records beats a banner announcing that the app is offline.
- Say when the data was last refreshed on any screen where a stale number could drive a bad decision.
- Never present a state the server can revoke as final. Draft saved is honest. Booking confirmed is not, until the server says so.
- Make rollback recoverable. A rejected write puts the person back in the editor with their content, not in a dialog that discards it.
- Disable actions that genuinely cannot work offline, as Android’s guidance recommends, rather than letting them fail on a timeout.
How do you test an offline-first app?
- Airplane mode is the easy case. The harder one is a live but useless connection: high latency, packet loss, or a captive portal answering every request with a login page.
- Kill the app process mid-drain and relaunch, then confirm nothing was lost and nothing was sent twice.
- Replay one queued operation twice and assert that a single record exists. That is the only real test of an idempotency key.
- Run two devices on one account, take both offline, edit the same record on each, and confirm the resolution rule behaves as designed rather than as it happens to. Test clock skew too.
What does offline support add to cost and timeline?
Offline support is a tax on every feature that writes data: a local schema and its migrations, an outbox, a sync scheduler on two platforms, idempotency on the server, a conflict rule per entity, and a test matrix carrying a connectivity axis. The backend half is scoped through Sigi’s custom software development practice. Sigi does not publish what any client paid. These are typical-scope planning estimates for a whole product, not the offline layer alone:
$40k–$80k
Estimate: focused MVP, one codebase, core flows only
Source: Typical-scope estimate, not a Sigi client invoice
$80k–$180k
Estimate: production iOS + Android, auth, payments, admin light
Source: Typical-scope estimate, not a Sigi client invoice
$150k–$350k+
Estimate: multi-surface marketplace (customer + worker + admin)
Source: Typical-scope estimate, not a Sigi client invoice
Read them as order-of-magnitude figures, alongside Sigi’s guide to how much it costs to build a mobile app. The question that moves the number is not whether the app should work offline. It is how many write paths have to survive a dead zone.
In what order should you build offline support?
- Decide, per write path, whether the device may assert the change or must ask the server. That list is the architecture.
- Put a real local database behind the screens, with a migration plan, and make the interface read from it exclusively.
- Add the outbox and idempotency keys together. An outbox without idempotency produces duplicates the first time a response is lost.
- Wire the scheduler: WorkManager on Android, Background Tasks on iOS, plus an opportunistic drain on returning to the foreground.
- Implement the conflict rule per entity, make the server enforce the constraints that must never be violated, and build the offline interface states and the connectivity tests as part of the feature.
Related reading
The mirror-image problem, keeping every surface current while the connection holds, is real-time tracking architecture. For the framework decision underneath these builds, see Flutter vs React Native. Offline comes up early in how to build a delivery app and how to build a warehouse management system. The proof here is the agritech marketplace case study, the counterexample the 3DLogistiX warehouse platform case study. To scope a build, start with mobile app development at Sigi.

