Mobile Architecture
Offline-first Mobile Platform
How a field-services enterprise stopped losing inspections to dead zones.
The brief
“Every inspection our teams complete in a basement or a parking structure is money on the table until it syncs. We’ve lost forms, we’ve double-billed clients, and our inspectors have learned not to trust the app. We need software that works where they work — and where they work, there’s usually no signal.”
That was the problem, in the client’s words: a field-services and inspections enterprise with hundreds of devices in daily use, whose most valuable work happened precisely where connectivity didn’t.
Context & constraints
The operating reality shaped every technical decision:
- Connectivity is the exception, not the rule. Inspectors routinely spend full working hours underground, in stairwells, or on remote sites.
- Zero tolerance for lost field data. A lost inspection form isn’t an inconvenience — it’s rework, a billing dispute, and eroded trust in the tooling.
- A mixed, budget Android fleet. Storage, memory, and battery budgets were real constraints, not footnotes.
- One team, many apps to come. The platform had to be a foundation — its architecture and components would seed future field applications, not serve a single product.
The architecture
Three decisions defined the platform.
1. Local-first, not cache-first
Most “offline support” is a cache bolted onto a network-first app — usable until the network disappears mid-workflow. This platform inverted the model: the on-device database is the system of record, and the network is a replication concern. Every write commits locally in milliseconds; the UI never waits on a request; a sync engine reconciles with the backend whenever connectivity allows.
Conflict resolution was designed per entity type rather than globally: reference data is server-authoritative, inspection payloads are field-authoritative — the person standing on site knows best — and every reconciliation leaves an audit trail. Client-generated identifiers make server operations idempotent, so the same submission arriving twice can never create a duplicate record.
2. A durable operation queue
Sync has to survive everything a field day throws at it: app kills, dead batteries, reboots at 2% charge. Every mutation is recorded as an operation in a persisted outbox, committed in the same local transaction as the data change — the queue can never disagree with the database. When a connection appears, the queue drains in dependency order with exponential backoff and jitter; failed operations retry safely, and poison operations are parked for inspection instead of blocking everything behind them. The queue lives on disk, not in memory — a crash loses nothing.
3. A media pipeline built for bad networks
Inspection photos are the heaviest payloads and the most likely to fail. The pipeline treats them accordingly: images are compressed and downscaled at capture, stored outside the database with references inside it, and uploaded through chunked, resumable transfers — a dropped connection resumes where it stopped instead of starting over. Sync is prioritized by business value: small, structured inspection data goes first; media trickles behind it. Once safely uploaded, local originals are reclaimed to respect the fleet’s storage budgets.
Components & stack
A single Flutter/Dart codebase for the Android-first fleet · SQLite-backed local store · background workers scheduled through the platform’s native work managers · REST APIs with idempotency keys · a layered architecture (UI → application services → repositories → sync engine → transport) in which the sync engine is fully isolated from feature code — features write locally and never know sync exists.
Outcomes
- Zero lost field submissions since rollout. The failure mode the client described in the brief simply stopped occurring.
- Sync-related support tickets fell to near zero within two release cycles — and with them, the billing disputes they used to cause.
- The platform became the baseline. Its shared foundations seeded a component library reused across 16+ subsequent projects; new field applications now bootstrap in days rather than weeks.
- Reliability is enforced, not hoped for. The sync engine is covered by deterministic tests that simulate connectivity loss mid-operation, part of an engineering discipline sustaining a 98.3% test success rate.
Retrospective
Two things would be done differently today. Conflict visibility shipped later than it should have — early versions resolved conflicts silently, which was correct but occasionally surprising to the people whose data was merged; “what changed and why” belongs in the first release, not a later one. And sync observability — per-device queue depth and failure dashboards — would be built before rollout rather than after the first support escalations. Both are now defaults in every platform that followed.
The patterns this platform proved — local-first storage, a transactional outbox, resumable media — are no longer a project. They’re the baseline every field application after it was built on.