synclayer 0.1.0-alpha.3
synclayer: ^0.1.0-alpha.3 copied to clipboard
A local-first sync SDK for Flutter with offline support, automatic conflict resolution, and real-time synchronization.
SyncLayer #
Build offline-first Flutter apps in minutes — Production-grade sync engine with automatic background synchronization and conflict resolution.
⚠️ ALPHA VERSION - Early release. APIs may change. See known limitations.
Why SyncLayer? #
Your users expect apps to work offline. But building sync is hard:
❌ Manual queue management
❌ Conflict resolution logic
❌ Network retry handling
❌ Version tracking
SyncLayer handles all of this for you.
// That's it. Your app now works offline.
await SyncLayer.init(
SyncConfig(
baseUrl: 'https://api.example.com',
collections: ['todos'],
),
);
// Save works instantly (local-first)
await SyncLayer.collection('todos').save({
'text': 'Buy groceries',
'done': false,
});
// Auto-syncs in background when online
// Handles conflicts automatically
// Retries on failure
What You Get #
🚀 Local-First - Writes happen instantly to local storage
🔄 Auto-Sync - Background sync every 5 minutes (configurable)
📡 Offline Queue - Operations sync automatically when online
⚔️ Conflict Resolution - Last-write-wins, server-wins, or client-wins
🔌 Backend Agnostic - Works with REST, Firebase, Supabase, or custom backends
📦 Batch Operations - Save/delete multiple documents efficiently
👀 Reactive - Watch collections for real-time UI updates
Quick Start #
1. Add dependency #
dependencies:
synclayer: ^0.1.0-alpha.2
2. Initialize #
import 'package:synclayer/synclayer.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await SyncLayer.init(
SyncConfig(
baseUrl: 'https://api.example.com',
syncInterval: Duration(minutes: 5),
collections: ['todos', 'users'], // Collections to sync
),
);
runApp(MyApp());
}
3. Use it #
// Save (works offline)
final id = await SyncLayer.collection('todos').save({
'text': 'Buy milk',
'done': false,
});
// Get
final todo = await SyncLayer.collection('todos').get(id);
// Update (same as save with id)
await SyncLayer.collection('todos').save({
'text': 'Buy milk',
'done': true,
}, id: id);
// Delete
await SyncLayer.collection('todos').delete(id);
// Watch for changes (reactive UI)
StreamBuilder(
stream: SyncLayer.collection('todos').watch(),
builder: (context, snapshot) {
final todos = snapshot.data ?? [];
return ListView.builder(
itemCount: todos.length,
itemBuilder: (context, i) => Text(todos[i]['text']),
);
},
);
How It Works #
┌─────────────┐
│ Your App │
└──────┬──────┘
│ save()
▼
┌─────────────┐ ┌──────────────┐
│ SyncLayer │────▶│ Local Storage│ (Instant)
│ │ │ (Isar) │
└──────┬──────┘ └──────────────┘
│
│ (Background)
▼
┌─────────────┐ ┌──────────────┐
│ Sync Engine │────▶│ Backend │ (Auto-sync)
│ + Queue │ │ API │
└─────────────┘ └──────────────┘
Architecture:
- SyncLayer - Main API (what you use)
- Collections - Data abstraction (like tables)
- SyncEngine - Background processor (handles sync)
- Queue Manager - Retry logic and ordering
- Conflict Resolver - Handles conflicts automatically
Example App #
See the Todo App example for a complete working app with:
- Offline editing
- Auto-sync when online
- Conflict resolution
- Real-time UI updates
Backend Integration #
SyncLayer works with any backend. You need two endpoints:
// Push: Receive changes from client
POST /sync/{collection}
Body: { recordId, data, version, updatedAt }
// Pull: Send changes to client
GET /sync/{collection}?since={timestamp}
Response: [{ recordId, data, version, updatedAt }]
See backend example for a complete Node.js implementation.
Works With #
- ✅ REST APIs (built-in adapter)
- ✅ Firebase (custom adapter)
- ✅ Supabase (custom adapter)
- ✅ GraphQL (custom adapter)
- ✅ Any backend (implement
SyncBackendAdapter)
Advanced Features #
Batch Operations #
// Save multiple
await SyncLayer.collection('todos').saveAll([
{'text': 'Task 1'},
{'text': 'Task 2'},
{'text': 'Task 3'},
]);
// Delete multiple
await SyncLayer.collection('todos').deleteAll([id1, id2, id3]);
Manual Sync #
// Trigger sync immediately (e.g., pull-to-refresh)
await SyncLayer.syncNow();
Conflict Resolution #
await SyncLayer.init(
SyncConfig(
baseUrl: 'https://api.example.com',
conflictStrategy: ConflictStrategy.lastWriteWins, // Default
// or: ConflictStrategy.serverWins
// or: ConflictStrategy.clientWins
),
);
Event Monitoring #
SyncLayerCore.instance.syncEngine.events.listen((event) {
switch (event.type) {
case SyncEventType.syncStarted:
print('Sync started');
break;
case SyncEventType.syncCompleted:
print('Sync completed');
break;
case SyncEventType.conflictDetected:
print('Conflict in ${event.collectionName}');
break;
}
});
Known Limitations #
This is an alpha release. Known issues:
- ⚠️ Pull sync requires explicit
collectionsconfiguration - ⚠️ Example backend uses in-memory storage (not production-ready)
- ⚠️ Limited production testing (2 of 10 validation tests completed)
- ⚠️ Basic error handling and retry logic
- ⚠️ No built-in authentication or encryption
See CHANGELOG for details.
Roadmap #
- ❌ Complete production validation tests
- ❌ Persistent backend example
- ❌ Custom conflict resolvers
- ❌ Encryption support
- ❌ WebSocket support for real-time sync
- ❌ Firebase/Supabase adapters
- ❌ Pagination for large datasets
vs Other Solutions #
| Feature | SyncLayer | Drift | Firebase | Supabase |
|---|---|---|---|---|
| Offline-first | ✅ | ✅ | ❌ | ❌ |
| Backend agnostic | ✅ | ✅ | ❌ | ❌ |
| Auto-sync | ✅ | ❌ | ✅ | ✅ |
| Conflict resolution | ✅ | ❌ | ✅ | ✅ |
| Queue management | ✅ | ❌ | ✅ | ✅ |
| Custom backend | ✅ | ✅ | ❌ | ❌ |
SyncLayer = Drift's offline-first + Firebase's auto-sync + Your own backend
Contributing #
Issues and PRs welcome! See CONTRIBUTING.md.
License #
MIT License - see LICENSE file.
Support #
- 📖 Documentation
- 🐛 Issues
- 💬 Discussions
Made with ❤️ by Hostspica