Offline first

Offline Mobile Apps: How Field Teams Keep Working Without a Connection

More and more of the projects we take on arrive with offline operation written into the requirements. Our working definition is that an app works offline when it can keep operating for extended periods without a connection, and the duration of that period is what drives the architecture, the data model and the cost.

September 7, 2026·8 min read
Offline first Flutter Field operations Scoping
Key takeaways
  • Plan distribution before synchronisation. On a data heavy product the first constraint is not merging changes, it is delivering the dataset at all. Split it into independently versioned units so devices download only what changed, and size the backend for the first download rather than for steady state.
  • Ship the parts that never change inside the build. Static reference data does not belong in the sync layer. Moving it into the application removes it from the traffic, from the failure cases and from the running cost.
  • Budget a field test, not a simulator run. Simulators verify logic under ideal conditions. Behaviour at the edge of coverage, with a drifting position and a session that has been open for hours, is only observable where the product will actually be used.
  • Automatic rules need a manual correction path. Whatever conflict policy you adopt, an administrator or the user has to be able to fix a record by hand, otherwise the exceptions the rules got wrong stay in the data permanently.

What does a client mean by offline?

Usually one of three things, and they sit far apart in cost. Showing data downloaded earlier. Accepting one submission and sending it when the signal returns. Or working for hours with no connection at all, which is the definition we use internally.

Most estimates are built for the first case and most expectations are built for the third. So the question we ask is not whether the app needs offline support. It is what the user is doing at the moment the connection disappears, and what they will be doing three hours later.

In field service the answer is concrete. Technicians go into basements and tunnels constantly. Those are places with no signal, and it is normal work rather than an exception, so the business logic has to be designed for those conditions from the start rather than patched afterwards.

Why the volume of data is the first real problem

The naive version of offline first says put everything on the device. On a navigation product covering Polish inland waterways that approach does not survive contact with reality. There was a lot of map data, and pulling all of it to every device would have been a disaster for the backend.

So the data was split by route segment, and each segment carries a version number. On start the app compares versions and downloads only the segments that changed, then keeps checking for updates on a regular basis. Nothing is fetched because a user asked for a region, and nothing is fetched twice.

The second decision was quieter and saved more. Anything that was certain not to change was kept inside the app itself rather than downloaded. That data leaves the synchronisation problem completely, and in our experience it is the option teams skip, because everything gets treated as something the server has to deliver.

The trade off we accepted

The first launch is deliberately slower, because it pulls most of the data at once. Every launch after that is only synchronisation.

The alternative was a fast first launch and a user who discovers the missing data on the water, which is exactly the moment they cannot download anything. Between a slow start once and a useless app at the wrong time, the choice is not close.

What we found only after taking the app onto the water

Field testing is where this project turned real. In the simulator everything worked. On the water the app lost its connection and showed the wrong position on the map, and that only surfaced once we physically took it out on a route.

That is the part worth carrying into any offline project. A simulator tests your code. It does not test your conditions, and in offline software the conditions are the product. Signal that fades rather than disappears, a GPS fix that drifts, a device that has been running for hours: none of that exists on a desk.

A product where offline was the whole point

Liniowiec is a navigation app for Polish inland waterways, built in Flutter with a Nest.js backend. Long stretches of those routes have no cellular coverage, and a navigation tool that stops working there is worse than carrying no tool at all.

Liniowiec, an offline river navigation app built in Flutter

Route data, marina information and hazard markers live on the device, so position, bridge clearances and warnings stay available with the connection gone. Positioning matches GPS to kilometer markers on a simplified river diagram, which means the app can place a user relative to the next hazard without asking a server anything.

Users also report hazards, a fallen tree or a bridge with reduced clearance. Those reports are separate entries rather than edits to a shared record, and the administration decides what becomes visible to everyone else. That single choice removed a whole class of conflicts before it could exist.

StackFlutter and Nest.js
Timeline4 months to launch
ConnectivityFull offline operation
Read the Liniowiec case study

How do you decide what happens when two devices disagree?

There are three honest answers, and the right one depends on who owns the truth in the business rather than on what is easiest to build.

  • Last write wins. The most recent upload replaces what was there. Acceptable when a record has one natural owner, for example a technician's own notes on their own job.
  • Field level merge. Two people editing different fields both keep their change. More work in the sync layer, and the right answer when a record is genuinely shared.
  • The server decides and the loser is told. Correct anywhere money, stock or scheduling is involved, because a silent overwrite there costs more than an interruption.

There is a fourth option that people forget, and it is often the best one: design the data so the conflict cannot happen. Entries that are appended rather than edited, with visibility controlled centrally, never need a merge rule at all.

Whichever of these you pick, the product needs a way to correct data by hand, through an admin panel or directly from the app. Automatic rules resolve the common cases. The uncommon ones end up wrong, and without an edit path they stay wrong forever.

What the queue on the device has to survive

Everything waiting to be sent is stored locally on the device, so killing the app does not damage the data. That is the baseline. A queue held in memory looks identical in a demo and loses a shift of work the first time the system reclaims memory.

Photos are worth separating. In field products we keep them in their own queue rather than mixed with form data, because a form row and a photo behave nothing alike on a weak connection. We also compress before upload. Sending originals is not a good solution and it raises the running cost of the product, which the client pays every month after launch.

Sessions need the same treatment. If a token expires during a disconnected shift, the app uses a refresh token once the connection returns, rather than signing the person out and putting the queued work at risk.

Four questions to answer before development starts

These are the ones we ask during scoping. Answering them early costs a conversation, and answering them later costs a change request.

  • Where does the queue live and what survives a force quit? If the answer is memory, a shift of work is one system decision away from disappearing.
  • Do photos travel in their own queue, and are they compressed? A form row and a photo behave nothing alike on a weak connection, and uncompressed uploads show up on the monthly bill.
  • What does the user see while a record is pending, synced or failed? A failed state also needs an action attached, because an error someone cannot resolve is just an interruption.
  • Who can correct data by hand once it is wrong? An admin panel or an edit path in the app, otherwise a bad merge stays in the data permanently.

When is offline the wrong thing to build?

When the work happens in places with coverage. An online product is faster to build, easier to reason about and cheaper to run, and the decision can be revisited once there is real usage data instead of assumptions. We ask about it directly during scoping rather than pricing an engine the users will never need.

A middle option covers a lot of products: read offline, queue a small number of critical writes, leave the rest online. That handles the elevator and the parking garage without paying for full synchronisation.

If people genuinely work away from signal, the opposite holds and half measures are worse than either extreme. That case belongs in offline first app development from the first architecture conversation, and it shows up most often in field service and inspection products.

FAQ

How much does offline support add to a project?

It depends which of the three meanings applies. Reading cached data is a small addition to most builds. Full offline operation changes the data layer, the distribution of data and the interface, so it is scoped as architecture rather than as a feature. The ranges sit on our pricing page.

Can offline support be added after launch?

Offline reading usually can. Offline working is close to a rebuild of the data layer, because the interface has to stop waiting for the network and start reading from a local store. If there is any chance the product needs it, decide before the first version.

What do you use for local storage in Flutter?

sqflite is our proven choice. The library matters less than the shape around it: a local database the interface reads from, a queue of pending changes stored on the device, and synchronisation running in the background rather than in the user's way.

Why is the first launch slower in an offline app?

Because it pulls most of the data at once, on purpose. It is a deliberate trade against the alternative, which is a fast first launch and missing data at the moment the user is out of range and cannot download anything.

How does the app know what to download later?

On our navigation product the data is split by route segment and each segment carries a version number. The app compares versions on start, downloads only what changed, and keeps checking for updates. Data that will never change ships inside the app instead.

What happens to queued work if the session expires?

The queue stays on the device and the app refreshes the session once a connection is available. Discarding queued work at sign in is how a full shift of work disappears, so that behaviour has to be decided deliberately rather than inherited from a library.

Thomas Siudut
Written by Thomas Siudut Co-Founder and CEO, Apps Value

Thomas sets the company's long term strategy and direction, identifies market opportunities, and owns business development and client acquisition. He builds strategic partnerships that last beyond a single project. He works with clients from the first strategy conversation, so their business goals, not just their feature list, drive every decision.

Not sure which kind of offline your product needs?

Describe what your users are doing when the signal disappears. You will hear which of the three levels that requires, what it changes in the build, and what we would leave out of a first version.

Book an intro call 30 minutes, no preparation needed.