offline_first_sync_drift 0.1.1 copy "offline_first_sync_drift: ^0.1.1" to clipboard
offline_first_sync_drift: ^0.1.1 copied to clipboard

Offline-first synchronization library for Dart/Flutter built on Drift. Features conflict resolution, outbox pattern, and incremental sync.

example/example.dart

// ignore_for_file: avoid_print

import 'dart:async';

import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:offline_first_sync_drift/offline_first_sync_drift.dart';

part 'example.g.dart';

// 1. Define your model
class Todo {
  final String id;
  final String title;
  final bool completed;
  final DateTime updatedAt;
  final DateTime? deletedAt;
  final DateTime? deletedAtLocal;

  Todo({
    required this.id,
    required this.title,
    required this.completed,
    required this.updatedAt,
    this.deletedAt,
    this.deletedAtLocal,
  });

  factory Todo.fromJson(Map<String, dynamic> json) => Todo(
        id: json['id'] as String,
        title: json['title'] as String,
        completed: json['completed'] as bool,
        updatedAt: DateTime.parse(json['updated_at'] as String),
        deletedAt: json['deleted_at'] != null
            ? DateTime.parse(json['deleted_at'] as String)
            : null,
        deletedAtLocal: json['deleted_at_local'] != null
            ? DateTime.parse(json['deleted_at_local'] as String)
            : null,
      );

  Map<String, dynamic> toJson() => {
        'id': id,
        'title': title,
        'completed': completed,
        'updated_at': updatedAt.toIso8601String(),
        if (deletedAt != null) 'deleted_at': deletedAt!.toIso8601String(),
        if (deletedAtLocal != null)
          'deleted_at_local': deletedAtLocal!.toIso8601String(),
      };

  TodosCompanion toInsertable() => TodosCompanion.insert(
        id: id,
        title: title,
        completed: completed,
        updatedAt: updatedAt,
        deletedAt: Value(deletedAt),
        deletedAtLocal: Value(deletedAtLocal),
      );
}

// 2. Define table with SyncColumns mixin
@UseRowClass(Todo, generateInsertable: true)
class Todos extends Table with SyncColumns {
  TextColumn get id => text()();
  TextColumn get title => text()();
  BoolColumn get completed => boolean().withDefault(const Constant(false))();

  @override
  Set<Column> get primaryKey => {id};
}

// 3. Database with SyncDatabaseMixin
@DriftDatabase(
  include: {'package:offline_first_sync_drift/src/sync_tables.drift'},
  tables: [Todos],
)
class AppDatabase extends _$AppDatabase with SyncDatabaseMixin {
  AppDatabase() : super(NativeDatabase.memory());

  @override
  int get schemaVersion => 1;
}

// 4. Implement TransportAdapter (or use offline_first_sync_drift_rest)
class MockTransport implements TransportAdapter {
  @override
  Future<PushResponse> push(PushRequest request) async =>
      PushResponse.success(request.payload, DateTime.now().toUtc());

  @override
  Future<PullResponse> pull(PullRequest request) async =>
      PullResponse(items: [], nextCursor: null);

  @override
  Future<Map<String, dynamic>?> fetch(String kind, String id) async => null;

  @override
  Future<bool> health() async => true;
}

Future<void> main() async {
  final db = AppDatabase();

  // Create SyncEngine
  final engine = SyncEngine(
    db: db,
    transport: MockTransport(),
    tables: [
      SyncableTable<Todo>(
        kind: 'todo',
        table: db.todos,
        fromJson: Todo.fromJson,
        toJson: (t) => t.toJson(),
        toInsertable: (t) => t.toInsertable(),
      ),
    ],
    config: const SyncConfig(
      conflictStrategy: ConflictStrategy.autoPreserve,
    ),
  );

  // Listen to events
  final subscription = engine.events.listen((event) {
    switch (event) {
      case SyncStarted(:final phase):
        print('Sync started: $phase');
      case SyncCompleted(:final stats):
        print('Sync completed: pushed=${stats.pushed}, pulled=${stats.pulled}');
      case ConflictDetectedEvent(:final conflict):
        print('Conflict: ${conflict.entityId}');
      default:
        break;
    }
  });

  // Create a todo locally
  final todo = Todo(
    id: 'todo-1',
    title: 'Learn offline_first_sync_drift',
    completed: false,
    updatedAt: DateTime.now().toUtc(),
  );

  await db.into(db.todos).insert(todo.toInsertable());
  print('Created todo: ${todo.id}');

  // Add to outbox for sync
  await db.enqueue(UpsertOp(
    opId: 'op-1',
    kind: 'todo',
    id: todo.id,
    localTimestamp: DateTime.now().toUtc(),
    payloadJson: todo.toJson(),
  ));
  print('Added to outbox');

  // Sync
  final stats = await engine.sync();
  print('Sync stats: pushed=${stats.pushed}, pulled=${stats.pulled}');

  // Cleanup
  await subscription.cancel();
  engine.dispose();
  await db.close();
}
0
likes
160
points
--
downloads

Publisher

unverified uploader

Offline-first synchronization library for Dart/Flutter built on Drift. Features conflict resolution, outbox pattern, and incremental sync.

Repository (GitHub)
View/report issues

Topics

#drift #offline-first #sync #database #cache

Documentation

API reference

License

MIT (license)

Dependencies

drift

More

Packages that depend on offline_first_sync_drift