Offline-First React Native Apps for Field Data Collection

Suman SharmaSuman Sharma

Agriculture, climate monitoring, and supply-chain apps all share one condition: the people using them are often out of signal. A farm registration app that fails without 4G is useless where it's needed most.

The principle: local first, sync later

  1. The UI reads and writes only to the local database.
  2. Every change is recorded in an outbox.
  3. A sync worker sends the outbox when online and pulls server changes.

The network becomes an optimisation, not a requirement.

Local storage

OptionGood for
expo-sqliteDefault choice: relational, reliable
WatermelonDBLarge datasets, reactive queries, sync primitives
MMKV / AsyncStorageSmall key-value settings, not records

The outbox

type OutboxItem = { id: string; // client-generated UUID entity: "plot" | "harvest" | "photo"; operation: "create" | "update"; payload: unknown; createdAt: number; attempts: number; };

Sync loop:

  1. Take the oldest items.
  2. Send them with their client-generated IDs.
  3. On success, mark them done. On failure, back off and retry.

Idempotency matters. The server must accept the same item twice without creating duplicates, because retries will happen. Client-generated UUIDs make this easy.

Photos and large files

Save to the device file system, queue uploads separately, and upload with resumable or chunked uploads when on Wi-Fi if possible. Never block a form on a photo upload.

Conflicts

StrategyWhen
Last write winsSimple fields, low risk
Append-only eventsHarvests, measurements, logs (no conflicts by design)
Field-level mergeDifferent people edit different fields
Manual reviewCritical data like plot boundaries

For traceability data, I prefer events over mutable records. See Agricultural Supply-Chain Traceability.

UX that builds trust

  • Show sync status per record: saved on device, syncing, synced, or failed.
  • Show last successful sync time.
  • Never say "Saved" when you mean "queued".
  • Let users retry failed items.
  • Work on cheap Android devices with limited storage.

Frequently asked questions

What's the simplest offline-first architecture?

Write everything to a local database first, record each change in an outbox table, and sync the outbox to the server when online. The UI always reads from the local database.

How do I handle conflicts?

Decide per entity. Last-write-wins is simple and fine for many fields. Append-only events avoid most conflicts. For critical data, flag conflicts for human review.

Which local database should I use?

SQLite (for example expo-sqlite) is a solid default. Libraries built on it, such as WatermelonDB, add reactive queries and sync helpers for larger datasets.

Contact

Get in Touch

Want to call me? book a call and I'll respond in the available time slot. I will ignore all soliciting.