Offline Support
Phones lose signal. People go into tunnels. They walk into concrete buildings. They board planes. They go camping. If your app stops working the moment Wi-Fi cuts out, those moments make people uninstall.
Offline support means deciding how much your app keeps working when there's no internet. And gracefully reconciling when the network comes back.
Three levels of offline
You don't have to support all of these. Pick the one that fits your app.
Level 1: Read-only
The user can open the app and see whatever they last loaded. No new data until they're back online. They can't change anything either.
Good for: content apps, news, anything where users mostly read.
Level 2: Queue and sync
The user keeps using the app. They can add, edit, even delete things. Those actions wait quietly on the phone until the network returns, then sync up.
Good for: journaling, habit tracking, note-taking, lightweight productivity.
Level 3: Fully offline-first
The phone is the source of truth. The server is a backup the phone syncs with whenever it can. The user gets a first-class experience even if they go a week between connections.
Good for: field-work apps, rural deployments, travel apps, anything used away from networks.
Most apps want level 2. Level 3 is a real commitment with trade-offs.
What you ask for
Level 1 (read-only)
"Cache the habit list so it's visible when the app opens offline. Don't let users check off habits offline; show a friendly 'Connect to keep going' message."
Level 2 (queue and sync)
"Let users check off habits offline. Queue the check-ins locally. When the network returns, sync them to the server in order. Show a small indicator when there are queued items."
Level 3 (full offline-first)
"Full offline-first setup. Use a local database on the phone as the source of truth. Sync with the server in the background. On conflict (same row edited in two places), keep the most recent change."
What the user actually sees
The quiet signals matter more than the machinery underneath. The agent adds, by default:
- An offline indicator: a small pill at the top of the screen: "You're offline. Changes will sync when you reconnect." It disappears when the network is back.
- Disabled buttons for actions that need a connection: with a tooltip explaining why.
- A "last updated 2 hours ago" timestamp on cached lists, so users know how fresh the data is.
- A "synced" confirmation when a queued action actually reaches the server. Usually subtle: just making the row look saved.
When two devices edit the same thing
If a user has your app on their phone and tablet, and they edit the same item on both while one is offline, you have a conflict when they reconnect.
The agent handles this for you with a simple, sensible default: the most recent change wins. This works well for almost every app where conflicts are rare. For apps where this matters (a shared document, a collaborative whiteboard), there are smarter strategies. Ask the agent about it.
Realtime + offline
These play together well. When the user comes back online:
- Anything they queued while offline gets sent to the server.
- The live connection reconnects.
- Anything that happened while they were offline arrives.
- Their screen updates to match reality.
See Realtime Updates for the live side.
Testing offline
To test, you simulate "no internet":
- iPhone simulator: there's a Network Link Conditioner setting in developer tools.
- Android emulator: turn on airplane mode in the emulator settings.
- Real device: airplane mode.
Go offline, do 10 things, come back online, confirm everything synced in the right order. This is the easiest test to forget and the easiest one to embarrass yourself with.
How much can the phone remember?
A phone's storage is finite. Apps that cache everything eventually fill up. Be sensible:
- Cache recent activity (30-90 days), not forever.
- Cap the queue at a sensible size (say, 500 pending actions) and alert the user if it's much deeper. Usually means something is broken.
- Don't cache huge files (over 10 MB).
"Cache the last 30 days of journal entries locally. Drop older ones from the cache. They're still on the server."
Things that catch people out
- A queued action that fails permanently. If the server rejects something (a validation rule has changed, say), don't swallow the error. Surface it to the user with a clear retry option.
- Clock drift. "Most recent change wins" assumes accurate clocks. Phones can have wrong clocks. Use the server's time for tiebreakers when possible.
- Retry storms. A phone that keeps failing to sync on a flaky network drains the battery. The agent uses exponential backoff and a sensible retry cap.
Under the hood (for engineers)
The agent picks the right local store for the level:
- AsyncStorage for tiny key-value (settings, flags).
- MMKV (
react-native-mmkv) for fast caches and small queues. Default for level 1 and 2. - SQLite (
expo-sqlite) when you need a real relational store. Level 3. - WatermelonDB when you want a tested sync framework on top of SQLite.
Network state from @react-native-community/netinfo; an offline banner subscribes to it. Queue persistence in MMKV survives force-quit. Last-write-wins is implemented with an updated_at column on every syncable row.
Next
Read Payments, or App Configuration for the icon, splash screen, and permissions.
