Skip to content
Blog
Choosing and buying custom software

What changes in a system when the app has to work with no signal

Pedro CunhaPublished on updated on 14 min read

In short

Offline-first is not an app feature: it is a decision that changes the database, the server and the product scope — above all, what does not go onto the device. And an app that promises offline and freezes is worse than an honestly online one: the difference between the two lives in details that almost never show up in a proposal.

What offline-first is, and why it is not a cache#

A cache stores the response of a request that already happened, so it does not have to be repeated. It serves to speed up reading and disappears the moment writing is involved: what the user creates with no signal has nowhere to live.

Offline-first is something else. The app has its own database on the device, and that is what the screen reads from — list, search, map, form. The server comes in afterwards, in the background. The network stops being a condition for the work to happen.

The test takes ten seconds: in airplane mode, try to create something. An app with a cache fails to save; an offline-first app registers it, and the record is still there after closing and reopening.

The best-known formulation of this idea is the essay "Local-first software: you own your data, in spite of the cloud", published by the Ink & Switch lab in 2019, with Martin Kleppmann among the authors. Of the seven ideals it lists, the most relevant one for business software is also the simplest: the operation responds without depending on a round trip to the network.

When offline-first is worth it — and when it is money thrown away#

The criterion is not the company's industry. It is where the work happens.

A field sales rep covering the countryside of three states works where signal is a hypothesis, not a fact. An operator running equipment checklists on a construction site, likewise. A student logging a workout at the gym is usually in the basement. In those cases, the moment the system is used is precisely the moment the network is not there. And the reverse holds: if the person uses the system sitting down, on Wi-Fi, offline-first is permanent complexity in exchange for a problem they do not have.

The second question, once the first one answers "yes", is what needs to work without a network. Reading only is one problem; recording is another, much bigger one, because it requires an outbound queue and everything that comes with it.

DecisionWhat it imposesWhen it is worth it
Local database on the devicespace and memory on the device; an explicit cut of which data comes downthe work happens far from a reliable network
Write queue (send later)ordering between records, retries on failure and a policy for rejected writesthe person records in the field, not just reads
Identifier generated on the devicethe server stops being the one that names the recordany offline creation
Sync made visible in the interfacescreens and copy that most apps do not havealways — it is what stops the user thinking it froze

None of those lines is optional once the first one is chosen. That is why offline-first is a scope decision, not fine-tuning at the end of the project.

Why a bad offline app is worse than an honestly online one#

The system Reticar Motores used before ours already had offline behaviour — and that was exactly the source of the pain: sync fired on every launch and blocked the entire app while it ran, got worse on a slow or unstable connection, and the user had no way of knowing what was happening or whether what they recorded had gone up.

The effect on a team is predictable and expensive: people stop trusting the app and go back to writing on paper to type in later. An honestly online app, one that says "no internet" and does not pretend otherwise, generates less rework than an app that promises autonomy and delivers a freeze.

The difference between the two is not conceptual — it is about granularity, and it comes down to four implementation decisions:

  • Download in small pages. In the Reticar app, each download page carries 100 records. Our base project uses much larger pages, and for a large dataset that means holding the interface for too long.
  • Apply in batches. When the page is large, records are written to the local database in batches of 40, not all at once.
  • Give the screen back to the user between batches. Between one batch and the next, the app hands processing back to the interface so it can handle gestures and animation. That is the line separating "syncing in the background" from "it froze".
  • Show what is happening, by name. The sync screen states the phase (uploading or downloading) and the collection in plain language — "Downloading customers", "Downloading service orders". Someone who waits knowing what they are waiting for does not assume the app died.

The result of that sum, at Reticar: the first sync — the heaviest one, because it brings the whole initial load — takes on average under 10 seconds for a typical sales rep, on the client's own Android device and over a mobile network. The following ones are far faster, because they only bring what changed.

What changes inside the app#

Three structural changes, and none of them is visual.

The screen starts reading from the local database. The list does not call the API: the interface observes the device's database and updates itself when it changes — either because the user created something or because sync brought news. That is what makes searching across thousands of records instantaneous, with or without a network.

Writing becomes two steps. The record is saved to the local database and queued; the queue goes up when there is a network. For the user, the entry was finished the moment they hit save.

The record is born with its own identity, generated on the device before any contact with the server. Without that, nothing created offline could be referenced by another record until the app managed to ask "what number did you give this?".

There is a decision of ours with a direct consequence on scope. We use RxDB as the reactive data layer, and its official persistent storage for SQLite is a commercial plugin — part of the Premium licences, with a trial version limited to 500 documents and no indexes, which does not work for a base of thousands of records. Our way out was to run RxDB in memory and maintain, on our own, a SQLite mirror on the device.

The consequence of that is the part that matters to whoever is paying: the synced base occupies device memory. Deciding what comes down to the device stops being only a question of download time and becomes a memory budget. That is what makes the next section mandatory rather than advisable.

What changes on the server#

This is the part that usually surprises people: most of the offline-first work is not in the app.

A traditional API answers "give me the customer list". An offline app asks something else: "what changed since the last time we spoke?" — and the server has to deliver creations, changes and deletions since a reference point the app itself keeps.

Three requirements follow from that:

  • Deletion becomes a marking, not a removal. If the record simply vanishes from the database, it does not show up in the list of changes and the app never finds out it needs to take it off the user's screen.
  • Order matters. Records that depend on others have to go up and come down in the right sequence — the region before the customer, the customer before the visit.
  • Upload before download. The app first sends what is in the queue and only then asks what is new — in the reverse order, the server's old version comes back over a change that has not been uploaded yet.

And there is the permission-based cut, the most underestimated optimisation in this area. At Reticar, the sales rep only receives on the device what they have the right to see: the visits they are responsible for and the customers in the regions they are assigned to. The base has 3,819 customers; what comes down to each phone is a fraction of that — which improves download, memory use and security at the same time, because data that does not come down cannot leak if the device is lost.

And the effort deserves to be sized honestly: instrumenting each entity to take part in the sync is manual work. On the Reticar project, that server module runs past 1,300 lines. It is not a switch you flip.

What does not go onto the device#

This is the decision that saves the most money on an offline project, and it belongs to product, not engineering. The criterion is not how important the data is — it is whether it has an end.

Type of dataGoes onto the device?Real example
Catalogue and reference dataYes, read-onlyservices, parts and regions in the Reticar app; exercises and foods in the Team Garin app
User's operational data, boundedYes, with a round tripthe rep's visit; the student's profile and questionnaire
Unbounded historyNoworkouts and diets already completed: the completion is queued and sent, but the history is fetched from the API when the student asks
Data requiring real time or integrationNoqueries to a third-party system, and the AI module of the training app
Data that is not the user'sDoes not come downcustomers from other regions, in the rep's app

The history line is the one that generates the most debate in a scoping meeting, and it is the easiest to defend: history grows forever, and nothing that grows forever fits on a device. That is how we defined the offline scope of the app for sports consultancy Team Garin, now becoming a SaaS product: the student needs to record today's workout with no signal — they do not need to carry two years of workouts in their pocket to do that.

The same reasoning, applied to volume, solved the field app of DGA Intelligence's genetic management system: the herd is too large to come down whole, so the app syncs only the animals with an active status, and anything leaving that set is removed from the device on the next sync.

There is also the case where offline has to cover the entire operation because it genuinely happens with no signal: that is what we do in the asset and hardware management app for GT, whose operation we visited in the field to redesign the app — the record of that visit is on YouTube.

Conflict, and what happens to the write the server rejects#

Two people edit the same record with no signal. When they reconnect, the most common pattern — and the one we use — is simple: the last change to reach the server wins, with no merge and no warning. For registration and field records, where each person touches what is theirs, that is enough. For collaborative editing of the same document it is not — and merging field by field costs considerably more, so the decision belongs to scoping.

There is a second case, less discussed and more uncomfortable: the write the server rejects for good. A record created offline that breaks a validation rule will be rejected today, tomorrow and always. Because the queue is ordered, that entry blocks everything behind it.

The usual way out is to discard the entry after a number of attempts, to unblock the rest. That is reasonable, and it has a side effect that needs saying out loud: the record stays on the device marked as unsynced, forever — with no explanation of why and no path to resolve it. In the Reticar app, each service order carries that state in its own icon, synced or not; what the icon does not tell you is whether there is still hope.

There is no single answer — there is a conscious decision. Warning the user, sending the record to a quarantine area or accepting the loss are paths with different costs; the mistake is not choosing. Offline-first guarantees the team can work with no signal; on its own it does not guarantee that everything typed arrived.

Frequently asked questions#

Is offline-first the same as caching or a PWA?#

No. A cache stores the response of a request that already happened and serves to speed up reading. Offline-first means the app has its own database on the device as the source of what appears on screen, including what the user creates with no signal. The practical test: in airplane mode, an app with a cache shows what has already been seen; an offline-first app lets you create something new, and that record survives closing and reopening the app.

My app already has an offline mode. Is that enough?#

It depends on how it behaves on a bad connection, which is different from no connection. Many apps with an offline mode sync everything on launch and block the interface while that happens — on a slow network, the result is worse than having no offline at all. The signs that it is not enough are: the app freezes on opening, takes a long time without explaining what it is doing, or does not make clear whether what was recorded has been uploaded.

Can offline be added after the app is already built?#

Yes, but it is not a tweak to the app: it is a change on the server too. The API has to start answering "what changed since this point", accept records with an identifier generated by the device itself, and mark deletions instead of actually removing them. That is why the offline conversation starts with the backend team, not the mobile one.

Do I need offline if my team only works in urban areas?#

Probably not for the reason you are thinking, but possibly yes for another one. The criterion is not the city, it is where the work happens: basements, warehouses, lifts, car parks and industrial buildings kill the signal inside a capital city. If the person uses the system sitting down, on Wi-Fi, offline-first is cost with no return.

What happens if two people edit the same record offline?#

In the most common pattern, the last change to reach the server wins, with no warning and no merge. That is acceptable for registration and field records, where each person touches what is theirs, and insufficient for collaborative editing of the same document. It is worth deciding this during scoping, because the alternative — merging changes field by field — costs considerably more.

Sources#

Next step#

If your team works where the signal drops, the starting point is not picking a technology: it is deciding what needs to work without a network and what does not go onto the device. That is how we replaced the field sales system at Reticar Motores — and it is the kind of decision we work through in custom systems.

Share

Keep reading

More on Choosing and buying custom software

Contact

Let's talk about your project

Tell us what you need to solve. We reply fast, with people who understand both technology and business.

Prefer to talk directly?

Pick the channel you prefer. We reply fast, during business hours.

From the first conversation to go-live: efficiency, security and innovation.