PocketBase Sync
A robust, offline-first synchronization library for PocketBase and Flutter.
This package provides a PocketBaseSyncManager that handles the complexities of syncing local data with a PocketBase backend, including conflict resolution, bandwidth optimization, and remote deletion support.
Features
- Offline-First: Read and write data locally (e.g., using Drift or Hive) and sync when online.
- 3-Way Merge Strategy: Automatically resolves conflicts. Server changes win on collision, but non-conflicting local changes are preserved.
- Remote Deletion: Detects records deleted on the server and removes them locally during sync.
- Bandwidth Optimized:
- Incremental Pull: Only fetches records updated since the last sync.
- Differential Push: Uses
HybridDiffer(Myers diff) to send only changed fields to the server.
- Tombstones: Handles local deletions by marking records as deleted and removing them from the server during sync.
- Auto-Cleanup: Automatically cleans up expired tombstones to keep the local database tidy.
Installation
Add pocketbase_sync to your pubspec.yaml:
dependencies:
pocketbase_sync: ^0.0.1
pocketbase: ^0.18.0
Usage
1. Implement SyncRepository
You need to implement the SyncRepository interface to tell the manager how to store and retrieve data locally. This is typically done with a local database like Drift or Hive.
class MyRepository implements SyncRepository<MyModel> {
// ... implement CRUD operations ...
@override
Future<List<SyncRecord<MyModel>>> getAll() async {
// Return all records (including deleted/messy ones)
}
@override
Future<DateTime> getLastSyncTime() async {
// Return stored last sync timestamp
}
@override
Future<void> setLastSyncTime(DateTime time) async {
// Persist new sync timestamp
}
}
2. Initialize the Manager
Create an instance of PocketBaseSyncManager, providing your PocketBase client, collection name, and repository.
final pb = PocketBase('https://my-app.pockethost.io');
final repository = MyRepository();
final manager = PocketBaseSyncManager<MyModel>(
pb: pb,
collection: 'notes',
repository: repository,
// Mappers to convert between your Model and JSON
fromJson: (json) => MyModel.fromJson(json),
toJson: (model) => model.toJson(),
);
3. Perform Operations
Use the manager to perform operations instead of writing directly to your local DB or SDK. The manager ensures changes are tracked for sync.
// Create
await manager.create(
manager.generateId(),
MyModel(content: 'Hello World')
);
// Update
await manager.update('RECORD_ID', updatedModel);
// Delete
await manager.delete('RECORD_ID');
4. Sync
Call sync() to push local changes and pull remote updates. You can do this on an interval, on app start, or manually.
try {
await manager.sync();
print('Sync completed successfully');
} catch (e) {
print('Sync failed: $e');
}
How it Works
Push (Local to Remote)
- Iterates through local records marked as
isDirty. - If
isDeletedis true, deletes the record on the server. - Otherwise, calculates a diff between the current data and the
baseData(state at last sync). - Sends a PATCH request with only the changed fields.
Pull (Remote to Local)
- Fetches records from PocketBase updated
updated >= lastSyncTime. - Reconciliation: Fetches a list of all IDs from the server to detect deletions.
- Merge:
- If a record exists locally and remotely, it performs a 3-way merge.
- If server data conflicts with local changes, server wins.
- If no conflict, updates are applied.
- Updates the local
baseDataand clears theisDirtyflag for synced records.
License
MIT