dart_data 0.1.1
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 ===');
}