Mobile API Best Practices · Offline-First & Sync

Offline-First Architecture

Local database as the source of truth for the UI, with background sync.

In an offline-first app the UI reads and writes a local database (SQLite/Room/Core Data/Realm) and a sync engine reconciles it with the server. Writes are recorded in a local outbox of pending mutations with client-generated IDs (UUIDv7/ULID) and idempotency keys, then replayed when connectivity returns. The UI shows optimistic state with pending/failed markers.

Connectivity strategies

Criterion Online-only Cache-then-network Offline-first
Works offline No Read-only, stale Read + write
Perceived latency Network RTT Instant for cached screens Instant
Complexity Low Medium High (sync, conflicts)
Server API needs CRUD ETags / cache headers Delta sync, idempotent mutations, conflict rules
Best for Banking transfers, checkout News, catalogs Notes, field work, messaging, tasks
@Entity(tableName = "pending_mutations")
data class PendingMutation(
    @PrimaryKey val id: String = UUID.randomUUID().toString(),   // also the Idempotency-Key
    val entityId: String,                                         // client-generated ID (ULID)
    val op: String,                                               // "create_task", "update_task"
    val payloadJson: String,
    val baseVersion: Long?,                                       // version the edit was based on
    val createdAt: Long = System.currentTimeMillis(),
    val attempts: Int = 0,
)

Common pitfalls

  • Server-generated IDs that the client needs before it can reference new records offline.
  • Retrying non-idempotent mutations (duplicate orders).
  • No UI for permanently failed mutations.

Key points

  • Local DB drives the UI
  • Outbox of idempotent mutations
  • Client-generated IDs

Further reading: Android: Build an offline-first app

Practice questions

Why do offline-first apps use client-generated IDs?

Answer

So records created offline can be referenced immediately (e.g. a comment on a new task) and so retries of the create are naturally idempotent.

What is a mutation outbox?

Answer

A local, ordered queue of pending writes, each with an idempotency key, that a sync engine replays to the server when connectivity returns.

Open in the app to mark lessons complete, take notes and track your progress.