Offline Sync Engine
Offline-first CRDT-based sync engine for Flutter and Dart applications. It keeps data synchronized across devices with deterministic conflict resolution.
Features
- 🔄 Automatic Sync - Push local operations and pull remote operations.
- 📴 Offline-First - Create/update/delete records without network.
- 🔀 Deterministic Merge - Concurrent updates converge predictably.
- 📱 Multi-Device - Same account on multiple devices stays in sync.
- 🎯 Operation-Based - Sync is based on replayable operations.
- 🔢 Vector Clocks - Causality tracking across devices.
- ⚡ Idempotent Apply - Safe against duplicate operation delivery.
- 🧩 Adapter-Based - Plug in your own database and cloud backend.
- ✅ Type Safe - Null-safe Dart API.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Your App │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ SyncManager │
│ - createOrUpdate() │
│ - delete() │
│ - sync() │
└────────────┬────────────────────────┬───────────────────────┘
│ │
▼ ▼
┌────────────────────────┐ ┌────────────────────────┐
│ DatabaseAdapter │ │ CloudAdapter │
│ (Your Implementation) │ │ (Your Implementation) │
│ │ │ │
│ - SQLite │ │ - REST API │
│ - Hive │ │ - Firebase │
│ - SharedPreferences │ │ - Supabase │
│ - ObjectBox │ │ - GraphQL │
└────────────────────────┘ └────────────────────────┘
Installation
dart pub add offline_sync_engine
or
flutter pub add offline_sync_engine
Your pubspec.yaml:
dependencies:
offline_sync_engine: ^3.0.0
Then run:
dart pub get
or
flutter pub get
Quick Start
import 'package:offline_sync_engine/offline_sync_engine.dart';
void main() async {
final manager = SyncManager(
database: InMemoryDatabaseAdapter(),
cloud: InMemoryCloudAdapter(),
deviceId: 'device_123',
);
// Local write (works offline)
await manager.createOrUpdate('user_1', {
'name': 'John',
'email': 'john@example.com',
});
// Push + pull sync
await manager.sync();
}
Core Concepts
1) Adapters
You provide two adapters:
DatabaseAdapter: local persistence (SQLite, Hive, Isar, etc.)CloudAdapter: backend transport (REST/Firebase/Supabase/etc.)
For demos/tests, use built-ins:
InMemoryDatabaseAdapterInMemoryCloudAdapter
2) SyncManager
SyncManager is the main entry point:
final manager = SyncManager(
database: myDatabaseAdapter,
cloud: myCloudAdapter,
deviceId: 'unique_device_id',
);
await manager.createOrUpdate('note_1', {'title': 'hello'});
await manager.delete('note_1');
await manager.sync();
final syncing = manager.isSyncing;
3) Conflict Resolution
The engine uses vector clocks with deterministic merge:
- If one version dominates another, dominant record wins.
- If versions are concurrent, fields are merged deterministically.
- Merge result is stable and commutative for concurrent records.
Production Checklist (Important)
Before publishing your app with custom adapters:
EnsuresaveOperation+applyOperationare durable.Keep operation IDs unique and indexed.MakeisAppliedfast (index/table/set).Use retries/backoff for cloud calls.Add authentication/authorization at transport layer.Add pagination/incremental pull strategy on backend.Add monitoring for sync failures.
Running Examples
cd example
dart run main.dart
dart run multi_device_example.dart
dart run delete_example.dart
dart run custom_adapters_example.dart
More details: example/README.md
Adapter Contract Notes
DatabaseAdapter expectations
saveOperationshould persist operation before app crash risk.getUnsentOperationsshould return unsent operations reliably.markOperationSentshould be idempotent.isAppliedshould be idempotency source-of-truth.applyOperationmust be deterministic and safe to replay.
CloudAdapter expectations
pushshould accept duplicate deliveries safely.pullcan return already-seen operations; manager handles dedupe viaisApplied.- Server ordering should be stable where possible.
License
MIT License - see LICENSE.
Libraries
- offline_sync_engine
- Offline-first CRDT-based sync engine for Flutter and Dart