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

The SwiftData for Flutter. Zero-boilerplate, offline-first data persistence and sync framework with SQLite storage, reactive streams, 6 conflict resolution strategies, encryption, and pluggable backen [...]

example/dart_data_example.dart

// ignore_for_file: avoid_print

/// A runnable example demonstrating dart_data core features.
///
/// Run with: `dart run example/main.dart`
/// (from the packages/dart_data directory)
library;

import 'dart:async';

import 'package:dart_data/dart_data.dart';

// ---------------------------------------------------------------------------
// Models
// ---------------------------------------------------------------------------

class Todo {
  final String id;
  final String title;
  final bool completed;
  final int priority;

  Todo({
    required this.id,
    required this.title,
    this.completed = false,
    this.priority = 0,
  });

  Todo copyWith({String? id, String? title, bool? completed, int? priority}) =>
      Todo(
        id: id ?? this.id,
        title: title ?? this.title,
        completed: completed ?? this.completed,
        priority: priority ?? this.priority,
      );

  @override
  String toString() =>
      'Todo(id: ${id.substring(0, 8)}..., title: $title, '
      'completed: $completed, priority: $priority)';
}

class TodoSchema extends ModelSchema<Todo> {
  @override
  String get modelName => 'Todo';

  @override
  String get tableName => 'todos';

  @override
  List<ColumnDefinition> get columns => const [
    ColumnDefinition(
      name: 'id',
      dartName: 'id',
      sqliteType: SqliteType.text,
      isPrimaryKey: true,
    ),
    ColumnDefinition(
      name: 'title',
      dartName: 'title',
      sqliteType: SqliteType.text,
      isIndexed: true,
    ),
    ColumnDefinition(
      name: 'completed',
      dartName: 'completed',
      sqliteType: SqliteType.integer,
    ),
    ColumnDefinition(
      name: 'priority',
      dartName: 'priority',
      sqliteType: SqliteType.integer,
    ),
  ];

  @override
  List<IndexDefinition> get indexes => const [
    IndexDefinition(name: 'idx_todos_title', columns: ['title']),
  ];

  @override
  Todo fromJson(Map<String, dynamic> json) => Todo(
    id: json['id'] as String,
    title: json['title'] as String,
    completed: (json['completed'] as int) != 0,
    priority: json['priority'] as int? ?? 0,
  );

  @override
  Map<String, dynamic> toJson(Todo model) => {
    'id': model.id,
    'title': model.title,
    'completed': model.completed ? 1 : 0,
    'priority': model.priority,
  };

  @override
  String getId(Todo model) => model.id;
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------

Future<void> main() async {
  print('=== dart_data Example ===\n');

  // ------------------------------------------------------------------
  // 1. Initialize with in-memory SQLite
  // ------------------------------------------------------------------
  print('1. Initializing DartData...');
  final fd = await DartData.initialize(
    databasePath: ':memory:',
    schemas: [TodoSchema()],
  );
  print('   Done.\n');

  // ------------------------------------------------------------------
  // 2. Get a Repository
  // ------------------------------------------------------------------
  final repo = fd.repository<Todo>(TodoSchema());

  // ------------------------------------------------------------------
  // 3. CRUD Operations
  // ------------------------------------------------------------------
  print('2. CRUD Operations');

  // Create — empty id auto-generates a UUID
  final todo1 = await repo.save(
    Todo(id: '', title: 'Buy groceries', priority: 2),
  );
  final todo2 = await repo.save(Todo(id: '', title: 'Write docs', priority: 1));
  final todo3 = await repo.save(Todo(id: '', title: 'Deploy app', priority: 3));
  print('   Created 3 todos.');

  // Read by ID
  final found = await repo.findById(todo1.id);
  print('   Found by ID: $found');

  // Update
  final updated = await repo.save(todo1.copyWith(completed: true));
  print('   Updated: $updated');

  // Count
  final count = await repo.count();
  print('   Total count: $count\n');

  // ------------------------------------------------------------------
  // 4. Queries with QueryBuilder & OrderBy
  // ------------------------------------------------------------------
  print('3. Queries');

  // Filter: only incomplete todos
  final active = await repo.findAll(
    where: QueryBuilder().where('completed', equals: 0),
  );
  print('   Active todos: ${active.length}');

  // Order by priority descending
  final byPriority = await repo.findAll(
    orderBy: OrderBy('priority', descending: true),
  );
  print(
    '   By priority (desc): ${byPriority.map((t) => '${t.title}(${t.priority})').join(', ')}',
  );

  // Pagination
  final page = await repo.findAll(
    orderBy: OrderBy('priority', descending: true),
    limit: 2,
    offset: 0,
  );
  print('   First page (limit=2): ${page.map((t) => t.title).join(', ')}\n');

  // ------------------------------------------------------------------
  // 5. Watch for Changes
  // ------------------------------------------------------------------
  print('4. Watching Changes');

  final events = <String>[];
  final sub = repo.watchAll().listen((todos) {
    events.add('watchAll fired: ${todos.length} todos');
  });

  // Give the stream time to emit initial value
  await Future<void>.delayed(const Duration(milliseconds: 100));

  // Trigger a change
  await repo.save(Todo(id: '', title: 'New task'));
  await Future<void>.delayed(const Duration(milliseconds: 100));

  await sub.cancel();
  for (final e in events) {
    print('   $e');
  }
  print('');

  // ------------------------------------------------------------------
  // 6. Batch Operations
  // ------------------------------------------------------------------
  print('5. Batch Operations');

  await repo.batch((ctx) async {
    await ctx.save(Todo(id: '', title: 'Batch item 1'));
    await ctx.save(Todo(id: '', title: 'Batch item 2'));
    await ctx.save(Todo(id: '', title: 'Batch item 3'));
  });
  final afterBatch = await repo.count();
  print('   After batch insert: $afterBatch todos\n');

  // ------------------------------------------------------------------
  // 7. Soft Delete
  // ------------------------------------------------------------------
  print('6. Soft Delete');

  await repo.delete(todo2);
  final afterDelete = await repo.count();
  final withDeleted = await repo.findAll(includeDeleted: true);
  print(
    '   After soft-delete: $afterDelete visible, ${withDeleted.length} total',
  );

  // Hard delete
  await repo.delete(todo3, hard: true);
  final afterHard = await repo.findAll(includeDeleted: true);
  print('   After hard-delete: ${afterHard.length} total\n');

  // ------------------------------------------------------------------
  // 8. Encryption (spy runner — no SQLCipher needed)
  // ------------------------------------------------------------------
  print('7. Encryption (demo with SpyEncryptionPragmaRunner)');

  final spyRunner = SpyEncryptionPragmaRunner();
  final encFd = await DartData.initialize(
    databasePath: ':memory:',
    schemas: [TodoSchema()],
    encryptionConfig: EncryptionConfig(key: 'my-secret-key'),
    pragmaRunner: spyRunner,
  );
  print('   Encrypted DB initialized.');
  print('   PRAGMAs executed: ${spyRunner.executedPragmas}');

  await encFd.dispose();
  print('');

  // ------------------------------------------------------------------
  // 9. Schema Migration (v1 → v2)
  // ------------------------------------------------------------------
  print('8. Schema Migration');

  // Simulate a v2 schema that adds a "priority" column
  // (already present in TodoSchema, so migration detects no diff on a
  // fresh DB — but demonstrates the MigrationConfig API)
  DartData.resetForTesting();
  final migratedFd = await DartData.initialize(
    databasePath: ':memory:',
    schemas: [TodoSchema()],
    migrationConfig: MigrationConfig(
      schemaVersion: 2,
      autoMigrate: true,
      migrations: [
        MigrationStep(
          version: 2,
          description: 'Set default priority for existing todos',
          up: (db) async {
            db.execute('UPDATE todos SET priority = 0 WHERE priority IS NULL');
          },
        ),
      ],
      onBeforeMigrate: (from, to) async {
        print('   Migrating from v$from to v$to');
        return true;
      },
      onAfterMigrate: (from, to) async {
        print('   Migration complete: v$from → v$to');
      },
    ),
  );

  final migrationResult = migratedFd.lastMigrationResult;
  if (migrationResult != null) {
    print(
      '   Result: ${migrationResult.autoMigrationsApplied} auto, '
      '${migrationResult.customMigrationsApplied} custom migrations applied',
    );
  }
  print('');

  // ------------------------------------------------------------------
  // Cleanup
  // ------------------------------------------------------------------
  await fd.dispose();
  await migratedFd.dispose();
  DartData.resetForTesting();

  print('=== Done ===');
}
1
likes
130
points
108
downloads

Documentation

API reference

Publisher

verified publisherflutterplaza.com

Weekly Downloads

The SwiftData for Flutter. Zero-boilerplate, offline-first data persistence and sync framework with SQLite storage, reactive streams, 6 conflict resolution strategies, encryption, and pluggable backend adapters.

Topics

#database #sqlite #offline-first #sync #persistence

License

BSD-3-Clause (license)

Dependencies

drift, meta, sqlite3, uuid

More

Packages that depend on dart_data