TOOKLI
Mobile

Offline-first is a data model decision, not a caching one

Bolting a cache onto an online-first mobile app produces conflicts you cannot resolve. Offline-first starts with identifiers and merge rules.

TOOKLI EngineeringEngineering team2 min read

The thing teams try first

The app fails outside coverage, so someone adds a cache. Reads work offline. Then writes need to work offline too, so writes get queued. Then two devices queue changes to the same record, and the system has no idea what to do with them.

At that point you are not building a cache. You are building a distributed database with no plan.

Start with identifiers

The single most important decision: the client generates the identifier, not the server.

If a technician creates a job in a tunnel and another creates one at the depot, both records need to exist and be distinguishable the moment they are created — not after a round trip that may never happen. A UUID generated on device costs nothing and removes an entire class of bug.

Then decide what "conflict" means for each field

There is no universal merge strategy, but there are only a few useful ones, and each field gets exactly one:

  • Last write wins — fine for a free-text note.
  • Server wins — right for anything the server derives, like a price.
  • Merge — for sets. Two people adding different photos should end with both.
  • Ask the human — for anything where a wrong answer costs money.

Write this table down per field. It is a five-minute conversation with the operations team that prevents six weeks of firefighting.

Design the conflict UI for the person, not the developer

When a human has to resolve a conflict, show them what changed, who changed it and when — then let them choose. The failure mode we see most often is a rule that silently overwrites data captured in the field with data typed in an office. Field data is usually the more accurate of the two, and the person who captured it never learns it was discarded.

The payoff

Done this way, connectivity stops being a feature and becomes an implementation detail. The app behaves the same in a basement and on office wifi, and the sync layer becomes something you can reason about instead of something you fear.

Related reading