Blog · Mobile
Mobile · Oct 2026 · 7 min read

Building for zero internet: lessons from an offline-first wholesale ERP

A wholesale client came to us needing real inventory and billing software — barcode scanning, GST-ready invoices, purchase tracking, running ledgers. The catch: their shop floor has patchy or zero internet for large stretches of the day. Most off-the-shelf ERPs assume a live connection to a cloud backend. That assumption doesn't survive contact with a real warehouse.

This is what we actually did to build Shop Inventory Management as a fully offline-first Kotlin Android app — and the parts of the design that turned out to matter more than expected.

The core decision: local database is the source of truth

The first and most important architectural call was treating the on-device database — not a remote server — as the authoritative source of truth for every screen in the app. Billing, inventory counts, ledgers: all of it reads from and writes to local storage first, with zero blocking calls to a network on the critical path of any user action.

If a feature's happy path requires a network round-trip to complete, it isn't actually offline-first — it's online-first with a loading spinner you hope resolves quickly.

What this changes about every feature

01

Barcode scanning & product lookup

Product data is cached locally in full, not fetched per-scan. A scan that depends on a live lookup is a scan that fails exactly when the shop floor needs it most.

02

GST billing & PDF invoices

Tax calculation and PDF generation happen entirely on-device. The invoice a customer walks out with can't depend on whether the shop's router is up.

03

Running ledgers & purchase records

Every transaction writes to local storage immediately and atomically — no "pending sync" state that a user has to understand or worry about.

04

Reports

All 12+ reports compute from the local dataset directly. No report waits on a network call, because none of the data it needs lives anywhere else by default.

What actually broke first

The naive version of "store everything locally" breaks in a specific way: if the app ever needs to run on more than one device for the same shop, you now have two sources of truth that can drift apart. We hit this early in testing — two staff members entering stock counts from different devices produced inventory numbers that didn't agree.

The fix wasn't a magic conflict-resolution algorithm. It was simpler and more honest: each device owns its own ledger of events (not final totals), and totals are always derived by replaying events, not stored as a single mutable number. Two devices can each append events locally without stepping on each other; reconciling them later is addition, not overwrite.

The lesson

Store the events that happened, not just the current state. A "current stock count" that gets overwritten is a conflict waiting to happen. A log of "+40 received, -12 sold, -3 damaged" can always be replayed into a correct total, from any device, in any order.

Sync, when it exists, is optional and additive

When the device does get connectivity, sync is a background operation that uploads the local event log and merges it with other devices' logs — it never blocks a user action waiting for that to finish, and the app is fully functional indefinitely with sync never running at all. That last part matters: for some shops, "offline-first" in practice just means "offline, full stop," and the app has to be fine with that being the permanent state.

Why this matters beyond one client

"Offline-first" gets used loosely to mean "has a cache." The actual test is simpler: does every core user action complete successfully with the device in airplane mode, indefinitely, with no degraded experience? If the answer is anything other than yes, the architecture is online-first with offline tolerance — which is a different, much weaker thing.

ML
Maa Labs EngineeringWritten from building a production offline-first Android app for a real client

Building something that can't depend on a live connection?

Offline-first architecture is a design decision, not a checkbox — let's talk about what yours actually needs.

Get a Free Quote →