← All articles
Mobile DevJune 18, 2026

Offline-First Mobile Apps with Expo SQLite and Background Sync

How to build mobile apps that work in tunnels and dead zones, then sync cleanly when the network returns.

By Kibet Brian

Users do not care about your network layer. They open the app in a basement parking lot and expect their data to be there. Offline-first means the local database is the source of truth and the server is a sync peer, not a dependency.

Model local state first

Every record gets a client-generated id, an updatedAt timestamp, and a syncStatus of clean, dirty, or conflicted. The UI only ever reads from SQLite, so renders are instant and never gated on fetch.

schema.sql
CREATE TABLE notes (
id TEXT PRIMARY KEY,
body TEXT NOT NULL,
updated_at INTEGER NOT NULL,
sync_status TEXT NOT NULL DEFAULT 'dirty'
};
CREATE INDEX idx_notes_sync ON notes (sync_status, updated_at);

Sync with a queue, not with hope

Writes go to SQLite first and enqueue a sync job. A background task drains the queue oldest first, pushes dirty rows, pulls server changes since the last cursor, and marks rows clean. If the network drops mid-drain, the queue simply waits, nothing is lost.

sync.ts
async function drainQueue() {
const dirty = await db.getDirtyRows();
for (const row of dirty) {
await api.push(row);
await db.markClean(row.id);
}
const cursor = await db.getCursor();
const remote = await api.pull({ since: cursor });
await db.applyRemote(remote);
}

Resolve conflicts with rules, not modals

Conflicts are rare if you sync often, but they need a deterministic rule. Last-writer-wins by timestamp covers most consumer cases. For collaborative fields, merge at the field level and keep a log so support can explain any outcome.

If your app shows a spinner on open, you built an online app with an offline error message.

Sync on reconnect, not on a timer

Polling for connectivity wastes battery and still misses the moment. Listen to network state changes and drain the queue the instant a usable connection returns, with exponential backoff when the network flaps.

Test the offline matrix

  • •Airplane mode toggled mid-write, then reopened minutes later.
  • •Flaky network that drops every third request.
  • •App killed halfway through a sync drain, then relaunched.
  • •Device clock skewed by hours, so timestamps cannot be trusted blindly.

If all four pass without lost or duplicated data, the offline story is solid. Everything else is polish on top of a foundation users can feel every time they open the app underground.

Share this article