Designing a Delivery App for Unreliable Networks, Not Just Offline
TIL that 'handle offline' undersold the actual problem — the failure mode that hurt drivers most was a flaky, half-connected state, not a clean offline/online boundary.
The Problem #
Early network handling in the driver app treated connectivity as binary: online, requests go through; offline, show a banner and block actions. Real driver conditions — moving between areas with patchy coverage — mostly aren't either state cleanly. A request can start, hang for ten seconds, and either eventually resolve or fail in a way that looks identical to the driver either way.
Context #
Drivers are moving through areas with inconsistent coverage for most of a shift, not sitting in one place with a stable connection or a clean loss of signal.
What I Tried #
Used NetInfo to detect offline state and disabled the "complete delivery" button when it reported no connection.
What Went Wrong #
NetInfo reporting "connected" doesn't mean a request will actually succeed quickly — a technically-connected but very slow or lossy network looked identical to a healthy one from NetInfo's perspective, so the button stayed enabled and drivers hit long hangs on requests that eventually timed out.
The Solution #
Stopped gating actions on connectivity detection and instead made every action locally durable first: a "complete delivery" tap writes to local state and a persistent upload/sync queue immediately, regardless of network state, and a background process retries the queue with backoff whenever a request actually succeeds or fails.
async function completeDeliveryAction(orderId: string) {
await db.markComplete(orderId); // durable locally, instantly
await syncQueue.enqueue({ type: "COMPLETE_DELIVERY", orderId }); // retried in background
}Why It Works #
The driver's action needs to feel instant and safe regardless of what the network is doing at that exact moment, because the network's exact moment-to-moment state isn't something the driver — or the app — can reliably know in advance. Durable local writes plus a retrying background queue make the network's reliability a background concern instead of something blocking the driver's next move.
Lessons Learned #
"Offline support" framed the problem too narrowly. The actual requirement was "the app should work the same regardless of network quality," and a binary online/offline check doesn't capture the messy, in-between states that make up most of a real shift.
What I Would Do Differently #
I'd design around "every user action is durable locally before it's synced" from the first version of the app, instead of retrofitting it after NetInfo-based gating proved unreliable in the field.
Related Concepts #
Local-first mutation, sync queues with retry/backoff, the gap between connectivity detection and request success.
Related content
Understanding React Native File URIs Before You Try to Upload One
TIL that a file:// URI from Expo's camera isn't a file you can send as-is — it's a path into a cache directory the OS can clear, and the upload code needs to treat it that way.
Android-Specific React Native Problems That Never Show Up on iOS
TIL that a React Native feature working perfectly in the iOS simulator is not evidence it'll work on Android — cleartext traffic, back-button handling, and permission dialogs all diverge silently.
Building React Native Apps with Expo: What the Managed Workflow Actually Buys You
TIL that starting a React Native app in Expo's managed workflow paid off immediately for camera/location access, but I still had to understand what 'ejecting' would cost before I needed it.