Flutter wrapper for synclib - a cross-platform SQLite library with automatic change tracking for syncing.
- π Automatic Change Tracking: Every write operation (INSERT, UPDATE, DELETE) is automatically tracked
- π± Cross-Platform: Support for Android, iOS, and Web (WASM)
- π Sync-Ready: Get pending changes and mark them as synced with simple API calls
- πͺ Bulk Operations: Efficient bulk remote operations for large data transfers
- π― Simple API: Clean, idiomatic Dart API wrapping the native C library
| Platform | Status |
|---|---|
| Android | β arm64-v8a, armeabi-v7a, x86, x86_64 |
| iOS | β arm64, x86_64 (simulator) |
| Web | β WebAssembly |
Add this to your pubspec.yaml:
dependencies:
synclib_flutter:
path: ../synclib_flutter # or your pathBefore using the plugin, you need to build the native libraries:
cd synclibc
./build-cross-platform.sh
cd ../synclib_flutter
./scripts/build_native.shThis will:
- Build the synclib C library for all platforms
- Copy the compiled libraries to the appropriate Flutter plugin directories
To build and set up the web version, you'll need Emscripten:
- Install Emscripten (if not already installed):
cd ~
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh- Build the WASM module:
cd /path/to/synclibc
source ~/emsdk/emsdk_env.sh # Make sure Emscripten is in PATH
./build-cross-platform.sh wasmThis will create:
build/wasm/synclib.js- JavaScript glue code (68KB)build/wasm/synclib.wasm- WebAssembly binary (1.2MB)
- Copy WASM files to Flutter plugin:
# Create web assets directory
mkdir -p /path/to/synclib_flutter/lib/src/web
# Copy WASM files
cp build/wasm/synclib.js /path/to/synclib_flutter/lib/src/web/
cp build/wasm/synclib.wasm /path/to/synclib_flutter/lib/src/web/- Verify pubspec.yaml includes web assets:
flutter:
assets:
- lib/src/web/synclib.js
- lib/src/web/synclib.wasmThe web assets are already configured in pubspec.yaml and will be automatically bundled when building for Flutter web.
import 'package:synclib_flutter/synclib_flutter.dart';
// Open database
final db = await SynclibDatabase.open('/path/to/database.db');
// Create a table (untracked DDL operation)
await db.exec('''
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
email TEXT
)
''');
// Insert with change tracking
await db.write(
tableName: 'users',
rowId: '1',
operation: SynclibOperation.insert,
sql: "INSERT INTO users (id, name, email) VALUES ('1', 'Alice', 'alice@example.com')",
data: '{"id":"1","name":"Alice","email":"alice@example.com"}',
);
// Read data from database (simple string-based)
// Use json() to convert JSONB columns to text
final users = await db.read('SELECT id, name, json(document) as document FROM users');
for (final user in users) {
print('User: ${user['name']}');
final doc = jsonDecode(user['document'] as String);
}
// OR read BLOB/JSONB columns directly with readRaw()
final rawUsers = await db.readRaw('SELECT id, name, document FROM users');
for (final user in rawUsers) {
print('User: ${user['name']}');
final documentBlob = user['document'] as Uint8List;
final doc = jsonb.decode(documentBlob);
}
// Get pending changes
final changes = await db.getPendingChanges(limit: 100);
for (final change in changes) {
print('Change: ${change.operation} on ${change.tableName}');
print(' Row ID: ${change.rowId}');
print(' Data: ${change.data}');
}
// Mark changes as synced
if (changes.isNotEmpty) {
await db.markSynced(changes.last.seqnum);
}
// Close database
await db.close();// Apply a change from another client (doesn't create local change record)
await db.applyRemote(
tableName: 'users',
rowId: '2',
operation: SynclibOperation.insert,
sql: "INSERT INTO users (id, name, email) VALUES ('2', 'Bob', 'bob@example.com')",
data: '{"id":"2","name":"Bob","email":"bob@example.com"}',
);For efficient batch imports:
await db.beginBulkRemote();
try {
for (final row in largeDataSet) {
await db.execBulkRemote(
"INSERT INTO users (id, name, email) VALUES ('${row.id}', '${row.name}', '${row.email}')"
);
}
await db.endBulkRemote(); // Commits transaction
} catch (e) {
await db.endBulkRemote(rollback: true); // Rollback on error
rethrow;
}Main database interface.
static Future<SynclibDatabase> open(String dbPath)- Open database connectionFuture<void> write({...})- Execute write operation with change trackingFuture<void> exec(String sql)- Execute SQL without change tracking (for DDL)Future<List<Map<String, dynamic>>> read(String sql)- Execute read-only query (string-based, use json() for BLOBs)Future<List<Map<String, dynamic>>> readRaw(String sql)- Execute read-only query with BLOB supportFuture<List<Change>> getPendingChanges({int limit = 100})- Get pending changesFuture<void> markSynced(int seqnum)- Mark changes as synced up to seqnumFuture<void> applyRemote({...})- Apply remote change without trackingFuture<void> beginBulkRemote()- Begin bulk remote operation modeFuture<void> execBulkRemote(String sql)- Execute in bulk remote modeFuture<void> endBulkRemote({bool rollback = false})- End bulk remote modeFuture<int> getSchemaVersion()- Get current schema versionFuture<void> setSchemaVersion(int version)- Set schema versionFuture<void> close()- Close database connection
Enum for operation types:
SynclibOperation.insertSynclibOperation.updateSynclibOperation.delete
Represents a tracked database change:
int seqnum- Sequence number (monotonically increasing)String tableName- Table nameString rowId- Row ID (primary key)SynclibOperation operation- Operation typeString? data- JSON-encoded row data (null for DELETE)
See the example directory for a complete working example. Run it with:
cd example
flutter runUses Dart FFI to call the synclib C library directly. The native libraries are compiled ahead of time and bundled with the app.
Uses WebAssembly (Emscripten) to run the synclib C library in the browser. The WASM module is loaded at runtime using JavaScript interop.