Part 6 · 1 chapters · ~8 min

Offline and Sync

Offline-first design, local databases (SQLite, Drift, Isar, Realm), a device-side outbox with client ids and idempotency keys, background sync (WorkManager, BGTaskScheduler) and OS limits, pulling changes with cursors, conflict resolution strategies, sync engines (PowerSync, ElectricSQL, Replicache), and what must never be done offline (authoritative balances).

8

A device outbox

code
// Drift (SQLite) table for pending operations
class Outbox extends Table {
  TextColumn get id => text()();                 // client-generated UUID = idempotency key
  TextColumn get kind => text()();               // 'create_expense'
  TextColumn get payload => text()();
  IntColumn get attempts => integer().withDefault(const Constant(0))();
  @override Set<Column> get primaryKey => {id};
}

Future<void> drain() async {
  for (final op in await db.pendingOps()) {
    try {
      final r = await api.post('/v1/${op.kind}', body: op.payload, headers: {'Idempotency-Key': op.id});
      await db.markSynced(op.id, serverVersion: r.version);
    } on TimeoutException { await db.bumpAttempt(op.id); break; }   // ambiguous: keep it, retry later with the same key
  }
}
// pull: GET /v1/changes?cursor=… returns rows changed since the cursor; apply with version checks

What stays online: a wallet balance shown offline is a cached display value, labelled with its age; spending decisions are made by the server ledger (Backend Architecture P4: never decide on a copy).

OFFLINE-FIRST: AN OUTBOX ON THE DEVICE
act locally, sync when the network returns
UIlocal DB (SQLite / Drift)outboxserversave expense (pending)
swipe the figure sideways, or tap expand for full screen
1/4
local first
Writes go to a local database immediately so the UI responds offline; the record is marked pending.
write locally firstUI never waits