offline first for bad networks: what a delivery rider app taught us
Contents
last year we rebuilt the rider app for a logistics client (react native this time, the client already had a RN team). riders move between areas where network is good, then 2G, then nothing, then a filling station wifi that says "connected" but nothing loads. the old app treated network as always on and riders were losing deliveries, double submitting, and calling dispatch to confirm everything manually
here's what the second version does differently. not theory, this is what survived 6 months with real riders
writes go to a local queue first, always
every action the rider takes (picked up, arrived, delivered, photo, signature) is written to local storage first (we used SQLite) with status pending. the UI updates immediately from local data. a separate sync worker drains the queue to the server. the screen never waits on the network to say "done"
every mutation has an idempotency key
generated on the phone when the rider taps the button, stored with the queued item, sent as a header. if a request times out we dont know if the server got it, so we retry with the SAME key and the server returns the original result instead of creating a second delivery confirmation. before this we had riders "delivering" the same parcel 3 times on bad network. (yes @arjunmehta you were right, you can stop now)
dont trust "connected"
NetInfo will happily say connected on that filling station wifi. we use connectivity changes only as a hint to TRY syncing, and the real signal is whether a small request to our own health endpoint succeeds within a few seconds. isInternetReachable exists too, but we still do our own check
backoff with jitter
when a whole area comes back online every rider's app retries at the same second and the API falls over. exponential backoff (1s, 2s, 4s... capped at a couple of minutes) plus random jitter fixed it. classic, but we had to learn it the hard way
small payloads first, photos separate
status updates are tiny json and go first. proof of delivery photos are compressed on device (we resize to around 1280px, jpeg quality ~70, enough for a dispute) and upload separately in the background, resumable. a 3MB photo should never block "delivered" from reaching dispatch
show sync state honestly
each item has a small icon: saved on phone, sent, confirmed by server. riders actually look at it. it cut calls to dispatch a lot because they can see their stuff is waiting, not lost
conflicts: decide per field
for most fields last write wins is fine. for anything with money (cash on delivery amounts) the server is the authority and the app shows a conflict if they disagree. never let two offline phones silently overwrite money
if i was starting again i would design the sync queue on day one instead of adding it after launch. adding offline later means touching every screen. omo, it was a long quarter
Written by
Tunde Adebayo
Mobile dev (Flutter + React Native) at an agency in Lagos. building fintech and logistics apps mostly. if your app is slow on a 2GB android phone, its not "fine". football on weekends
Get the best of #thumb_zone weekly
One email each Monday with the most useful posts. No spam; unsubscribe in one click.
Comments
Sign in to join the conversation.
Sign in to comment