Offline is a state, not an error. What it took to make the app open instantly with no network round-trip.
The assumption we refused to make
Most apps are built on a quiet assumption: the network is there. When it is not, you get a spinner, a retry button, or a screen that says something went wrong. For a family organiser that assumption fails at exactly the moments the app is most useful. In the basement level of a supermarket. On the bus. In a village where data comes and goes with the weather.
So we made a rule early: the app must open to something useful with no network round-trip at all. Not a cached error page, but the household’s actual lists, tasks and budgets, clearly labelled with when they were last synced.
What “open offline” really means
It sounds like caching. It is more than that. Three things have to be true.
- The last known state is on the phone, and it is trusted. Every list, task and budget the member may see is stored locally the moment it arrives. Opening the app reads that store first and paints from it. The network is consulted afterwards, if it is there.
- Changes made offline are real changes. Ticking an item off in the shop with no signal is not a request that might fail later. It is recorded locally, shown as done, and queued. The queue replays in order when the connection returns.
- Two people changing the same thing must end well. If one person ticks an item off in the shop while another renames it at home, both changes have to survive. We settled on a deterministic merge: per field, last writer wins, with the rule shared between the app and the server so they can never disagree about the outcome. Genuine conflicts, the rare cases where the rule cannot decide, are shown to a person rather than silently resolved.
Why we poll instead of push
Real-time updates are usually done with a socket held open to the server. On a metered connection, through the proxies that many mobile networks route traffic through, an open socket is fragile and expensive. We chose something older and duller: the app asks the server “anything since version 412?” while a screen is open and the app is in the foreground. When nothing has changed, the answer is two fields long. When something has, only the changes come back.
It is not glamorous, but it works on every network we have tested, it costs almost nothing when the household is quiet, and it stops entirely when the app is in the background.
Sign-in without a good connection
The same thinking applies before you are even in. A member who has signed in once can open the app again without the server, because the session is cached and verified locally until it can be refreshed. Phone-number sign-up sends a six-digit code by text message because that works on phones that have never seen an app store, and the code is validated in full on the server as well as on the phone.
What it cost us
Offline-first is not free. Every screen has to be designed with a stale state, an empty state and a “queued, not yet sent” state. Every write has to be idempotent so a replayed queue cannot double-log an expense. Testing takes longer because the interesting bugs live in the gaps between connections.
We think it is the single decision that most shapes how KinHolo feels. The app is calm because it never makes you wait for it.
Want the next one?