Offline-first apps for low-connectivity districts: what to decide early
If your users work in places where the signal comes and goes, the app has to treat the network as a bonus, not a requirement. In practice that means the phone's own database is the source of truth, every entry is saved locally before anything else happens, and syncing runs quietly in the background whenever a connection shows up. The hard part isn't the code. It's a handful of decisions you need to make before the first screen is designed.
I've written about the Room and WorkManager plumbing before. This note is the layer above it: what to decide early when the app is going to districts and villages where the network can drop for hours, based on two apps I've built that had to work without a reliable connection.
Why connectivity changes the design, not just the code
An app built in a city office on good Wi-Fi tends to assume the request will come back. Out in a rural block in Assam, or anywhere the monsoon knocks towers off for a day, it often doesn't. When an app is designed online-first, the failure shows up as spinners that never stop, forms that lose their contents and people giving up on the app.
Android's own architecture guide is direct about the fix: in an offline-first app, the local data source is the canonical source of truth, and higher layers should read only from it. Everything below follows from taking that rule seriously.
Decision 1: what has to live on the phone
Make a list of every screen and ask: does the user need this when there is no signal? For a field data app the answer is usually yes for the forms, the reference lists (villages, categories, product codes) and anything already captured. For a personal app it might be everything.
My two reference points sit at opposite ends:
- Solar Rooftop Registration App captured each installation on the phone first, with GPS location and four geotagged, timestamped photos (roof, panel array, inverter, meter), then synced to Firebase when connectivity returned. Large photo batches were queued.
- FYRA Finance, my offline-first double-entry ledger, never needs the network at all. The ledger lives in SQLDelight with SQLCipher encryption, and even receipt scanning with ML Kit runs on the device with no network requests.
Most business apps land in between. Decide it per screen, write it down, and the database design follows.
Decision 2: which writes are allowed to wait
The Android guide describes three write strategies, and choosing between them per action is one of the most useful things you can do early:
| Strategy | How it works | Good for |
|---|---|---|
| Online-only | Send to the server first, only save locally if it succeeds | Things that must be live, like a payment or a booking |
| Queued | Put the write in a queue and drain it with backoff when online | Logs and analytics nobody needs to see right away |
| Lazy | Save locally first, then queue the network update | Forms, records and anything the user must not lose |
For a low-connectivity app, nearly every user-facing action should be a lazy write. Online-only actions should be few, and the UI should say clearly when they can't run, instead of letting someone tap a button that silently fails.
Decision 3: how photos travel
Photos are usually what breaks sync on a weak connection. A form is a few kilobytes; one full-resolution photo from a phone camera is a few megabytes. Three things help:
- Resize on the device. Find the smallest size where a meter reading or a document is still legible, and store that. In the apps I build, a tuned CameraX setup does this so photos stay readable but small enough for mobile data.
- Separate the record from the files. Sync the small record first so the office can see it exists, then upload the photos in their own queue. If photos fail, the record isn't stuck behind them.
- Give each upload its own retry. One failed photo shouldn't restart the whole batch.
WorkManager handles the retrying well: a sync worker that returns a retry result is retried with exponential backoff, and it waits for a network connection before running. That's the pattern the Android guide uses in its own sample.
Decision 4: what happens when two copies disagree
If two people can edit the same record offline, or one person edits it on two phones, you will eventually get two versions. Pick the rule before you build.
The common default is last write wins: every record carries a timestamp, and the server keeps the newer one. Android's guide describes this, and Cloud Firestore does the same when several offline changes hit one document. That's fine for a survey form.
It's not fine for money or stock. If two people each record a sale against the same stock count while offline, overwriting the number loses one of them. The safer pattern is to store each change as its own entry and calculate totals from the entries. That's why FYRA Finance is a double-entry ledger rather than a table of balances: entries are added, not overwritten. Its import log also flags duplicates, so the same statement imported twice doesn't count twice.
Decision 5: the phone your users actually carry
Low connectivity and low-end phones often go together. Plan for 2 GB of RAM, a nearly full storage and an old Android version, and keep these in mind:
- Keep heavy work off the main thread with Coroutines and WorkManager, so the app doesn't freeze while it syncs.
- Watch on-device storage. Queued photos add up quickly, so delete them once the server confirms receipt and show the user how much is waiting.
- If the data is sensitive, encrypt it at rest. A phone in the field can be lost. FYRA Finance uses SQLCipher with keys held in the Android Keystore.
- Show sync status clearly: how many entries are waiting, when the last sync happened. People trust an offline app more when they can see it's holding their work.
Firebase or your own sync?
For a straightforward collection app, Firebase can get you far. On Android, Cloud Firestore keeps offline persistence on by default with a cache threshold of about 100 MB, and syncs writes when the device reconnects. The Solar Rooftop app synced to Firebase.
Build your own local database and sync queue when you need any of these: large or ordered photo uploads, conflict rules other than last write wins, encrypted local data, or data that should never leave the phone. You can still sync to Firebase or Supabase on the server side; the point is that the phone's database and queue are yours.
What I'd do
If you're planning an app for users in low-connectivity districts, this is the order I'd work in:
- List each screen and mark what must work offline.
- Mark each write as online-only, queued or lazy. Most should be lazy.
- Design the local database first, with a sync status and timestamp on every record.
- Split records from photos and give photos their own upload queue.
- Decide the conflict rule per data type, and use append-only entries for anything that's counted or summed.
- Test on the cheapest phone your users have, with mobile data switched off, then on, then off again halfway through a sync.
None of this is exotic. It's a set of choices that are cheap to make on day one and expensive to retrofit after users have lost data.
If you're building something for users on patchy networks in Assam or elsewhere, here's how I approach mobile app development.
Frequently Asked Questions
Q:What does offline-first mean for a mobile app?
The phone's own database is the source of truth. The app reads from it and writes to it first, and the network is only used to sync in the background when a connection is available. Android's official architecture guide recommends exactly this model.
Q:Is Firebase enough for an offline-first Android app?
For simple record collection it can be. Cloud Firestore keeps an offline cache on Android by default (about 100 MB before it starts cleaning up) and syncs writes when the device reconnects, with the last write winning. For large photo batches, strict conflict rules or encrypted local data, you usually want your own local database and sync queue.
Q:How do you upload photos from areas with weak mobile data?
Save the photo on the device first, resize it to the smallest size that stays readable, and put it in a separate upload queue that retries with backoff. Keep the record itself small so it can sync even when the photos can't.
Q:Should a field app be native Android or cross-platform?
If most users are on low-end Android phones and the app depends on the camera, GPS and background sync, native Kotlin gives you the most direct control over memory, storage and WorkManager. Cross-platform can work, but test it on the cheapest phone your users actually carry.
Q:How do you handle the same record edited on two phones while offline?
Decide the rule before you build. Last write wins, using a timestamp on every record, is the common default and is what Android's guide describes. For money or stock counts, append changes as separate entries instead of overwriting one number, so nothing is silently lost.
Related Engineering Notes
Matching Services Tracks
Working on something similar?
Let's collaborate to design custom PCB schematics, write deterministic FreeRTOS threads, or configure secure Next.js databases.