serverpod-offline-sync/example/offline_sync_demo/README.md

4.6 KiB

Offline Sync Demo

Desktop Flutter demo for serverpod_offline_sync. It runs two independent local replicas of one user's data side by side, plus a view of the server's merged truth, so you can drive A → server and B → server independently and watch them converge.

How the package is wired

If you are here to learn the package, read lib/src/offline_replica.dart first — it is the entire integration in ~60 lines, deliberately free of demo and UI code. Every replica follows the same three steps:

  1. Open a sync session. The generated client.createSyncSession(path, isDebugMode: kDebugMode, persistentUserId: …) opens the local SQLite database, runs client migrations, wraps the generated sync tables, and initializes CRDT tracking.
  2. Read and write generated models against that session, e.g. Person.db.insertRow(session, person) or session.db.find<Person>(). (The demo also routes these through a generic TableOps registry so its metadata-driven UI can touch any table without a per-type switch — that part is demo-only; the seed* methods in lib/src/demo_controller.dart show the plain form.)
  3. Synchronize through a Serverpod client. client.offlineSync.syncOnce(session, onMergeSuccess: …) for a one-shot push/pull, or client.offlineSync.syncContinuously(session, …) to stream until cancelled.

When disposing a replica, cancel its sync subscription and call session.close() to release the local database connection.

Connectivity is just which client you sync through: the demo swaps Client.httpClientOverride between a real transport and a failing one to simulate going offline — the local database is untouched either way.

The rest of lib/src layers a UI on top: demo_controller.dart (a ChangeNotifier orchestrating users, replicas, and the server panel), snapshot.dart / relationships.dart / table_ops.dart (the metadata-driven view of the synced schema), demo_view.dart / sheets.dart (widgets), and scenarios.dart (guided scripts).

What the UI shows

Each demo user owns two replicas (<user>-a.db, <user>-b.db) shown side by side. A replica is a local SQLite store with its own CRDT node id but the same sync space; replicas share nothing locally and only converge by syncing through the server — like two phones on one account (see the "i" button in the toolbar).

The layout is a scenario rail, Replica A, Replica B, and a Server panel, with a global toolbar on top and a status line at the bottom.

Toolbar

  • Connectivity toggles every client between a real and a failing HTTP transport (the offline switch).
  • Show hidden rows reveals locally hidden / soft-deleted (tombstoned) rows, shown struck-through and muted alongside the visible "user view".
  • Refresh reloads all three panels; Reset all wipes both replicas and the server space at once.
  • The i button explains replica isolation.
  • The app bar carries a light/dark toggle and an avatar menu for switching between or creating demo users.

Per replica

  • Sync runs one push/pull; Stream keeps syncing continuously until toggled off — drive A and B independently and watch them (and the Server panel) converge.
  • Add data creates a root row or attaches a child through a foreign key; the same menu also offers ready-made seeds (a city/person/address/town graph, a typed row, an FK chain). Each row carries inline attach child and re-parent / detach actions, and every edge shows its onDelete action (restrict / cascade / set null / …).
  • Reset wipes just that replica's local database (a fresh device); syncing afterwards re-pulls whatever the server still holds.
  • Click any row to open its full record, edit fields, or delete it.

Server panel

  • Shows the server's merged truth for the current space, fetched through the demoDebug.fetchSpaceSnapshot endpoint (reads run in the caller's space via transactionForUser). It refreshes after each sync and via its own refresh button.
  • Reset server space hard-clears every row and CRDT metadata record in the space; the seed buttons write a graph straight into the space via demoDebug.seedSpace, so you can reset a replica and sync to exercise the fetch-from-scratch flow.

Scenario rail

  • Guided scripts (restrict, set-null, cascade, unique-conflict, …) that run a sequence of these same actions step by step. Every step is also doable by hand.

Running

Simply run the serverpod server and the app will be started together:

cd test/serverpod_offline_sync_test_server
serverpod start