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:
- 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. - Read and write generated models against that session, e.g.
Person.db.insertRow(session, person)orsession.db.find<Person>(). (The demo also routes these through a genericTableOpsregistry so its metadata-driven UI can touch any table without a per-type switch — that part is demo-only; theseed*methods inlib/src/demo_controller.dartshow the plain form.) - Synchronize through a Serverpod client.
client.offlineSync.syncOnce(session, onMergeSuccess: …)for a one-shot push/pull, orclient.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
onDeleteaction (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.fetchSpaceSnapshotendpoint (reads run in the caller's space viatransactionForUser). 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