Offline-first data sync with conflict resolution, retry queues, and local caching
dart pub add philiprehberger_sync_engineOffline-first data sync with conflict resolution, retry queues, and local caching
Add to your pubspec.yaml:
dependencies:
philiprehberger_sync_engine: ^0.1.0
Then run:
dart pub get
import 'package:philiprehberger_sync_engine/sync_engine.dart';
final store = LocalStore();
final resolver = ConflictResolver(strategy: ConflictStrategy.latestWins);
final engine = SyncEngine(store: store, resolver: resolver);
store.put(SyncRecord(
id: 'user-1',
data: {'name': 'Alice', 'email': 'alice@example.com'},
updatedAt: DateTime.now(),
));
final result = await engine.sync(
push: (records) async {
// Send records to your API, return ids of successfully pushed records
final response = await api.push(records);
return response.successIds;
},
pull: () async {
// Fetch records from your API
return await api.fetchAll();
},
);
print('Pushed: ${result.pushed}, Pulled: ${result.pulled}');
// Remote always wins
final resolver = ConflictResolver(strategy: ConflictStrategy.remoteWins);
// Local always wins
final resolver = ConflictResolver(strategy: ConflictStrategy.localWins);
// Latest timestamp wins
final resolver = ConflictResolver(strategy: ConflictStrategy.latestWins);
// Custom logic
final resolver = ConflictResolver(
strategy: ConflictStrategy.custom,
customResolver: (local, remote) => remote.version > local.version ? remote : local,
);
await engine.sync(
push: pushFn,
pull: pullFn,
onProgress: (completed, total) {
print('$completed / $total');
},
);
final active = store.query(where: (r) => r.status == SyncStatus.synced);
final stats = store.statistics();
print('Total: ${stats.total}, Synced: ${stats.synced}');
| Class | Method / Property | Description |
|---|---|---|
SyncRecord | SyncRecord(id:, data:, updatedAt:) | Create a sync record |
SyncRecord | withStatus(status) | Copy with new status |
SyncRecord | incrementVersion() | Copy with version + 1 |
LocalStore | put(record) | Store a record |
LocalStore | putAll(records) | Store multiple records |
LocalStore | get(id) | Retrieve a record by id |
LocalStore | remove(id) | Remove a record |
LocalStore | all() | Get all records |
LocalStore | pending() | Get pending and modified records |
LocalStore | markSynced(id) | Mark record as synced |
LocalStore | markModified(id) | Mark record as modified |
LocalStore | query(where:) | Filter records with a predicate |
LocalStore | statistics() | Get aggregate counts |
LocalStore | count | Total record count |
LocalStore | clear() | Remove all records |
ConflictResolver | resolve(local, remote) | Resolve a conflict |
ConflictResolver | resolvedCount | Number of conflicts resolved |
RetryQueue | enqueue(record) | Add a record to retry |
RetryQueue | dequeueAll() | Get and clear all retryable records |
RetryQueue | pending() | View queued records |
RetryQueue | count | Number of queued records |
RetryQueue | clear() | Clear the queue |
SyncEngine | sync(push:, pull:, onProgress:) | Run a sync cycle |
SyncEngine | lastSyncResult | Result of the last sync |
SyncEngine | isSyncing | Whether a sync is in progress |
SyncResult | pushed, pulled, conflicts, retried | Individual counts |
SyncResult | total | Sum of all counts |
dart pub get
dart analyze --fatal-infos
dart test
If you find this project useful: