Rural users do not experience "slightly slow 4G." They experience gone. If the app only works online, the product fails in the field — even when the backend is healthy in Mumbai.
The problem
Auction and listing flows required several round trips. A dropped connection mid-submit created duplicate drafts, empty screens, and support tickets that looked like "the app is broken" when the network was the real culprit.
Design constraints
- Reads must work from local cache after the first successful sync
- Writes must be queued and idempotent
- Conflicts must be deterministic — last-write-wins is not always correct for money flows
- UI must show honest state: offline, syncing, conflict, failed
What we shipped
Hive (and similar local stores) held entities the user already opened. A sync worker drained a durable outbox when connectivity returned. Each mutation carried a client-generated id so retries did not create double bids.
UI → local write → outbox → sync worker → API → ack → mark synced
↑ |
└────────── conflict / retry ←─────────┘
Screens subscribed to local streams first. Network was an accelerator, not a dependency for opening yesterday's data.
Hard lessons
Optimistic UI without a conflict policy creates silent corruption. We marked money-adjacent actions as require-online while keeping browsing and draft edits offline-capable. That trade-off was clearer to users than a "sometimes syncs wrong" marketplace.
Battery and storage matter. Unbounded caches on low-end Android devices hurt more than a spinner. We capped cache by entity type and age.
Takeaway
Offline-first is product design. The sync model, conflict rules, and status copy are the architecture — Hive is just the storage.