bedrock_sync 0.1.0 copy "bedrock_sync: ^0.1.0" to clipboard
bedrock_sync: ^0.1.0 copied to clipboard

A backend-agnostic, offline-first sync engine for Flutter. Your data stays grounded on device and syncs to any backend — REST, Firebase, or Supabase — when online.

bedrock_sync #

A backend-agnostic, offline-first sync engine for Flutter.

Write data locally, sync to any backend when online — with built-in conflict resolution.

Features #

  • 📦 Offline-first — reads and writes work without internet
  • 🔌 Backend-agnostic — REST, Firebase, Supabase, or your own adapter
  • Real-time reactivewatch() streams update your UI instantly
  • 🔀 Conflict resolution — LastWriteWins, ServerWins, ClientWins, Merge, or Custom
  • 🔁 Auto retry — exponential backoff with configurable max retries
  • 🌐 Auto sync — triggers on connectivity restore and/or fixed interval
  • 🧩 Fully customizable — swap any component (store, queue, adapter, resolver)
  • 📊 Sync status stream — show live sync state in your UI

Getting Started #

dependencies:
  bedrock_sync: ^0.0.1

Quick Start #

import 'package:bedrock_sync/bedrock_sync.dart';

// 1. Initialize the engine
final engine = BedrockEngine(
  adapter: RestSyncAdapter(baseUrl: 'https://api.example.com'),
  config: SyncConfig(
    enableLogging: true,
    conflictResolver: LastWriteWinsResolver(),
  ),
);

await engine.init();

// 2. Write locally (works offline instantly)
await engine.write('todos', {'title': 'Buy milk', 'done': false});

// 3. Read from local store
final todos = await engine.read('todos');

// 4. Watch for real-time changes
engine.watch('todos').listen((todos) {
  setState(() => _todos = todos);
});

// 5. Manual sync
final result = await engine.sync();
print('Synced: ${result.syncedCount}');

Conflict Resolvers #

Resolver Behavior Best for
LastWriteWinsResolver Most recent timestamp wins Notes, drafts
ServerWinsResolver Server always wins Financial data
ClientWinsResolver Local always wins User preferences
MergeResolver Field-level merging Collaborative docs
CustomResolver Your own logic Complex rules
// Custom resolver example
SyncConfig(
  conflictResolver: CustomResolver(
    resolver: (local, server) async {
      // return whichever data you want to keep
      return local.data['priority'] > server['priority']
          ? local.data
          : server;
    },
  ),
)

Sync Adapters #

REST (built-in) #

RestSyncAdapter(
  baseUrl: 'https://api.example.com',
  defaultHeaders: {'Authorization': 'Bearer $token'},
  buildUrl: (collection, id) => '/v2/$collection/${id ?? ''}',
  transformRequest: (op) => {'payload': op.data},
  transformResponse: (res) => res['data'] as Map<String, dynamic>,
)

Firebase (custom) #

class MyFirebaseAdapter extends FirebaseSyncAdapter {
  final _firestore = FirebaseFirestore.instance;

  @override
  Future<SyncAdapterResult> push(SyncOperation op) async {
    final ref = _firestore.collection(op.collection);
    final doc = await ref.add(op.data);
    return SyncAdapterResult.success({'id': doc.id, ...op.data});
  }
  // ...
}

Custom adapter #

class MyGraphQLAdapter extends SyncAdapter {
  @override
  Future<SyncAdapterResult> push(SyncOperation op) async {
    // Send to your GraphQL / WebSocket / gRPC server
  }
  // ...
}

Custom Local Store #

Swap the default SQLite store for Hive, Isar, or anything else:

class HiveLocalStore extends LocalStore {
  @override Future<void> init() async { ... }
  @override Future<void> save(String collection, String id, Map<String, dynamic> data) async { ... }
  // ... implement all methods
}

BedrockEngine(
  adapter: myAdapter,
  store: HiveLocalStore(), // plug it in here
)

Configuration #

SyncConfig(
  maxRetries: 3,                              // retry failed operations 3 times
  retryStrategy: RetryStrategy.exponential,   // 2s → 4s → 8s
  syncTrigger: SyncTrigger.hybrid,            // sync on reconnect + every 5min
  syncInterval: Duration(minutes: 5),
  batchSize: 50,                              // operations per batch
  enableLogging: true,                        // debug logs
  onBeforeSync: () async => true,             // gate sync with custom logic
  onAfterSync: (count) => print('Synced $count'),
  onError: (e, stack) => Sentry.captureException(e),
)

Sync Status in UI #

StreamBuilder<SyncStatus>(
  stream: engine.statusStream,
  builder: (context, snapshot) {
    final status = snapshot.data;
    return Text(
      status?.isOnline == true ? 'Online' : 'Offline',
    );
  },
)

License #

MIT

0
likes
160
points
28
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A backend-agnostic, offline-first sync engine for Flutter. Your data stays grounded on device and syncs to any backend — REST, Firebase, or Supabase — when online.

Repository (GitHub)
View/report issues

Topics

#offline-first #sync #sqlite #networking #local-database

License

MIT (license)

Dependencies

connectivity_plus, dio, flutter, path, path_provider, rxdart, sqflite, uuid

More

Packages that depend on bedrock_sync