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.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:bedrock_sync/bedrock_sync.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // ─── Initialize BedrockSync ────────────────────────────────────────────
  final engine = BedrockEngine(
    adapter: RestSyncAdapter(
      baseUrl: 'https://jsonplaceholder.typicode.com',
      // Customize URL building to fit your API
      buildUrl: (collection, id) => id != null ? '/todos/$id' : '/todos',
    ),
    config: SyncConfig(
      enableLogging: true, // show logs in debug
      maxRetries: 3, // retry 3 times before failing
      retryStrategy: RetryStrategy.exponential, // 2s, 4s, 8s...
      syncTrigger: SyncTrigger.hybrid, // sync on reconnect + interval
      syncInterval: const Duration(minutes: 5),
      conflictResolver: LastWriteWinsResolver(), // swap to any resolver
      onAfterSync: (count) => debugPrint('✅ Synced $count operations'),
      onError: (e, stack) => debugPrint('❌ Sync error: $e'),
    ),
  );

  await engine.init();

  runApp(MyApp(engine: engine));
}

class MyApp extends StatelessWidget {
  final BedrockEngine engine;

  const MyApp({super.key, required this.engine});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'BedrockSync Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: TodoScreen(engine: engine),
    );
  }
}

// ─── Todo Screen ─────────────────────────────────────────────────────────────

class TodoScreen extends StatefulWidget {
  final BedrockEngine engine;

  const TodoScreen({super.key, required this.engine});

  @override
  State<TodoScreen> createState() => _TodoScreenState();
}

class _TodoScreenState extends State<TodoScreen> {
  final _controller = TextEditingController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  Future<void> _addTodo() async {
    final title = _controller.text.trim();
    if (title.isEmpty) return;

    await widget.engine.write('todos', {
      'title': title,
      'done': false,
      'updatedAt': DateTime.now().toIso8601String(),
    });

    _controller.clear();
  }

  Future<void> _toggleTodo(Map<String, dynamic> todo) async {
    await widget.engine.updateRecord(
      'todos',
      todo['_id'] as String,
      {
        ...todo,
        'done': !(todo['done'] as bool? ?? false),
        'updatedAt': DateTime.now().toIso8601String(),
      },
    );
  }

  Future<void> _deleteTodo(String id) async {
    await widget.engine.deleteRecord('todos', id);
  }

  Future<void> _manualSync() async {
    final result = await widget.engine.sync();
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(
            result.success
                ? '✅ Synced ${result.syncedCount} items in ${result.duration.inMilliseconds}ms'
                : '❌ Sync failed: ${result.errors.first.message}',
          ),
        ),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('BedrockSync Demo'),
        actions: [
          // Sync status indicator
          StreamBuilder<SyncStatus>(
            stream: widget.engine.statusStream,
            builder: (context, snapshot) {
              final status = snapshot.data;
              return Padding(
                padding: const EdgeInsets.only(right: 12),
                child: Row(
                  children: [
                    Icon(
                      status?.isOnline == true
                          ? Icons.cloud_done
                          : Icons.cloud_off,
                      color:
                          status?.isOnline == true ? Colors.green : Colors.red,
                    ),
                    if ((status?.pendingCount ?? 0) > 0)
                      Badge(
                        label: Text('${status!.pendingCount}'),
                        child: const Icon(Icons.sync),
                      ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
      body: Column(
        children: [
          // Status Banner
          StreamBuilder<SyncStatus>(
            stream: widget.engine.statusStream,
            builder: (context, snapshot) {
              final status = snapshot.data;
              if (status == null) return const SizedBox.shrink();

              Color color;
              String label;

              switch (status.state) {
                case SyncState.syncing:
                  color = Colors.blue.shade100;
                  label = '🔄 Syncing...';
                  break;
                case SyncState.synced:
                  color = Colors.green.shade100;
                  label = '✅ All synced';
                  break;
                case SyncState.offline:
                  color = Colors.orange.shade100;
                  label = '📵 Offline — changes saved locally';
                  break;
                case SyncState.error:
                  color = Colors.red.shade100;
                  label = '❌ Sync error: ${status.lastError}';
                  break;
                default:
                  color = Colors.grey.shade100;
                  label = status.pendingCount > 0
                      ? '⏳ ${status.pendingCount} pending'
                      : 'Ready';
              }

              return Container(
                width: double.infinity,
                color: color,
                padding:
                    const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                child: Text(label, style: const TextStyle(fontSize: 13)),
              );
            },
          ),

          // Add Todo Input
          Padding(
            padding: const EdgeInsets.all(16),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      hintText: 'Add a todo...',
                      border: OutlineInputBorder(),
                    ),
                    onSubmitted: (_) => _addTodo(),
                  ),
                ),
                const SizedBox(width: 8),
                FilledButton(
                  onPressed: _addTodo,
                  child: const Text('Add'),
                ),
              ],
            ),
          ),

          // Todo List — streams local changes in real-time
          Expanded(
            child: StreamBuilder<List<Map<String, dynamic>>>(
              stream: widget.engine.watch('todos'),
              builder: (context, snapshot) {
                if (!snapshot.hasData) {
                  return const Center(child: CircularProgressIndicator());
                }

                final todos = snapshot.data!;

                if (todos.isEmpty) {
                  return const Center(
                    child: Text('No todos yet. Add one above!'),
                  );
                }

                return ListView.builder(
                  itemCount: todos.length,
                  itemBuilder: (context, index) {
                    final todo = todos[index];
                    final done = todo['done'] as bool? ?? false;

                    return ListTile(
                      leading: Checkbox(
                        value: done,
                        onChanged: (_) => _toggleTodo(todo),
                      ),
                      title: Text(
                        todo['title']?.toString() ?? '',
                        style: TextStyle(
                          decoration: done ? TextDecoration.lineThrough : null,
                          color: done ? Colors.grey : null,
                        ),
                      ),
                      trailing: IconButton(
                        icon: const Icon(Icons.delete_outline),
                        onPressed: () => _deleteTodo(todo['_id'] as String),
                      ),
                    );
                  },
                );
              },
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: _manualSync,
        icon: const Icon(Icons.sync),
        label: const Text('Sync Now'),
      ),
    );
  }
}
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