CindelSyncAdapter you implement, pulls remote changes, and applies backend corrections back into the local database — all without any code change to your reads or watchers.
What Sync Is and Is Not
Understanding the boundaries of Cindel Sync will save you time when designing your backend. Sync gives you:- Local-first optimistic writes — every write is visible in the local database immediately, before any network round trip.
- Offline support — local writes still commit and reads return optimistic data while the device is offline.
- Backend corrections applied locally — if your backend normalizes or corrects a document, Cindel applies that truth locally and watchers fire automatically.
- A durable outbox — pending mutations survive app close and reopen. When connectivity returns, the scheduler retries automatically.
- A hosted sync server — you supply the backend.
- Conflict resolution rules — your backend decides who wins.
- Authentication or access control — your adapter is responsible for auth.
- Multi-tab coordination on Web — watcher delivery is single-tab even when sync is active.
Enabling Sync
Pass aCindelSyncConfig to Cindel.open. Everything else — reads, writes, watchers, transactions — stays the same.
CindelSyncConfig Fields
If you omit
clientId, Cindel persists an internal ID for this local database. Provide an explicit clientId when your backend needs to track which device sent a mutation — for example, to apply per-device access control or ordering rules.Sync Status
TheonStatusChanged callback receives a CindelSyncStatus object you can use for UI indicators and logging.
phase field is one of:
Use
pendingCount to show a “2 changes pending” badge. Use lastSyncAt to show a “last synced” timestamp. Always use typed reads and watchers as the source of application data — do not drive UI state from sync callback payloads.
Implementing CindelSyncAdapter
Implement the CindelSyncAdapter interface by providing two methods: push and pull.
Push Flow
push receives a CindelPushRequest containing the pending local mutations and expects a CindelPushResult in return.
CindelPushRequest fields:
clientId— the local client/device IDlastPulledCheckpoint— the last checkpoint this database pulledschemaVersionByCollection— current local schema versions by collection namemutations— the list ofCindelSyncMutationobjects to send
CindelSyncMutation contains:
Return a
CindelPushResult:
acceptedMutationIds— tells Cindel which pending mutations can be removed from the outbox.rejectedMutations— for mutations your backend will permanently never accept. Reserve rejections for business failures, not temporary network errors.correctedChanges— lets your backend return final truth immediately, such as a server-normalized document.checkpoint— optional on push. If provided, Cindel stores it as the newest known checkpoint.
Pull Flow
pull receives a CindelPullRequest and expects a CindelPullResult containing every remote change after the local checkpoint.
CindelPullRequest fields: clientId, checkpoint, collections, schemaVersionByCollection.
Return a CindelPullResult:
resetRequired: true only when the backend cannot safely continue from the client’s checkpoint. For normal incremental sync, leave it as the default false.
The
collection field in every remote change must match a schema registered at Cindel.open. Cindel refuses to apply changes for unknown collections.Offline Behavior
If your adapter cannot reach the server, throw an error frompush or pull. Cindel will catch it and retry later according to interval.
While offline:
- Local writes still commit immediately.
- Typed reads return local optimistic data.
- Watchers emit local changes.
pendingCountincreases as mutations queue in the outbox.- The sync
phasemay becomeoffline. - Pending mutations survive app close and reopen.
Supported Local Operations While Sync Is Active
Cindel records the following local operations as sync mutations:- Typed
putandputAll - Typed
deleteanddeleteAll - Query deletes
- Link replacements
Minimal Server Requirements
A production sync backend should provide:- Durable storage for accepted mutation IDs and document state.
- Idempotent
mutationIdhandling — if the same mutation arrives twice, accept it again without applying it twice. - Stable checkpoints — clients must be able to pull all changes after a given checkpoint reliably.
- Access control and authentication — your adapter handles auth; your backend enforces it.
- Conflict and correction rules — your backend decides the final document shape.
POST /sync/push— accepts local mutations, returns accepted IDs and optional corrections.POST /sync/pull— returns remote changes after a checkpoint.
Testing Sync
Use aFakeSyncAdapter with an online toggle to test sync behavior without a real server. See the Testing page for a full guide.
Common Mistakes
Generating a new clientId on every app start
Generating a new clientId on every app start
If your backend uses
clientId to track devices or enforce ordering, a new random ID on every launch means every run looks like a brand-new device. Previously sent mutations become orphaned — the backend has no way to deduplicate them. Generate the clientId once, persist it (e.g. in SharedPreferences), and reuse it across restarts.Updating UI from sync callbacks instead of watchers
Updating UI from sync callbacks instead of watchers
The
onStatusChanged and onError callbacks are for indicators and logging only. Do not drive application data from their payloads. Use typed Cindel watchers (watchCollection, watchObject, etc.) as the single source of truth for your UI — they fire automatically when remote corrections arrive.Accepting duplicate mutationIds on the server
Accepting duplicate mutationIds on the server
Your backend must treat
mutationId as an idempotent key. If the network drops after your backend applies a mutation but before the adapter returns, Cindel will retry with the same mutationId. Accepting it twice will corrupt data. Always check whether a mutationId was already applied before processing it again.Returning remote changes for unregistered collections
Returning remote changes for unregistered collections
Every collection name in
CindelRemoteUpsert, CindelRemoteDelete, or CindelRemoteReplaceLinks must match a schema you passed to Cindel.open. If the name doesn’t match, Cindel will refuse to apply the change. Make sure your server only returns collections the client has registered.Using query updates while sync is enabled
Using query updates while sync is enabled
updateFirst and updateAll are not supported when sync is active. They produce partial field patches, not complete document snapshots, so Cindel cannot build a valid mutation. Read the documents, modify them in Dart, and call put or putAll to write them back.