import 'package:drift/drift.dart'; import 'package:flutter/material.dart' show Colors; import 'package:riverpod/riverpod.dart'; import 'connection/connection.dart' as impl; import 'tables.dart'; // Manually generated by `drift_dev schema steps` - this file makes writing // migrations easier. See this for details: // https://drift.simonbinder.eu/docs/advanced-features/migrations/#step-by-step import 'schema_versions.dart'; // Generated by drift_dev when running `build_runner build` part 'database.g.dart'; @DriftDatabase(tables: [TodoEntries, Categories], include: {'sql.drift'}) class AppDatabase extends _$AppDatabase { AppDatabase() : super(impl.connect()); AppDatabase.forTesting(DatabaseConnection connection) : super(connection); @override int get schemaVersion => 3; @override MigrationStrategy get migration { return MigrationStrategy( onUpgrade: stepByStep( from1To2: (m, schema) async { // The todoEntries.dueDate column was added in version 2. await m.addColumn(schema.todoEntries, schema.todoEntries.dueDate); }, from2To3: (m, schema) async { // New triggers were added in version 3: await m.create(schema.todosDelete); await m.create(schema.todosUpdate); // Also, the `REFERENCES` constraint was added to // [TodoEntries.category]. Run a table migration to rebuild all // column constraints without loosing data. await m.alterTable(TableMigration(schema.todoEntries)); }, ), beforeOpen: (details) async { // Make sure that foreign keys are enabled await customStatement('PRAGMA foreign_keys = ON'); if (details.wasCreated) { // Create a bunch of default values so the app doesn't look too empty // on the first start. await batch((b) { b.insert( categories, CategoriesCompanion.insert(name: 'Important', color: Colors.red), ); b.insertAll(todoEntries, [ TodoEntriesCompanion.insert(description: 'Check out drift'), TodoEntriesCompanion.insert( description: 'Fix session invalidation bug', category: const Value(1)), TodoEntriesCompanion.insert( description: 'Add favorite movies to home page'), ]); }); } // This follows the recommendation to validate that the database schema // matches what drift expects (https://drift.simonbinder.eu/docs/advanced-features/migrations/#verifying-a-database-schema-at-runtime). // It allows catching bugs in the migration logic early. await impl.validateDatabaseSchema(this); }, ); } Future> search(String query) { return _search(query).map((row) { return TodoEntryWithCategory(entry: row.todos, category: row.cat); }).get(); } Stream> categoriesWithCount() { // the _categoriesWithCount method has been generated automatically based // on the query declared in the @DriftDatabase annotation return _categoriesWithCount().map((row) { final hasId = row.id != null; final category = hasId ? Category(id: row.id!, name: row.name!, color: row.color!) : null; return CategoryWithCount(category, row.amount); }).watch(); } /// Returns an auto-updating stream of all todo entries in a given category /// id. Stream> entriesInCategory(int? categoryId) { final query = select(todoEntries).join([ leftOuterJoin(categories, categories.id.equalsExp(todoEntries.category)) ]); if (categoryId != null) { query.where(categories.id.equals(categoryId)); } else { query.where(categories.id.isNull()); } return query.map((row) { return TodoEntryWithCategory( entry: row.readTable(todoEntries), category: row.readTableOrNull(categories), ); }).watch(); } Future deleteCategory(Category category) { return transaction(() async { // First, move todo entries that might remain into the default category await (todoEntries.update() ..where((todo) => todo.category.equals(category.id))) .write(const TodoEntriesCompanion(category: Value(null))); // Then, delete the category await categories.deleteOne(category); }); } static final StateProvider provider = StateProvider((ref) { final database = AppDatabase(); ref.onDispose(database.close); return database; }); } class TodoEntryWithCategory { final TodoEntry entry; final Category? category; TodoEntryWithCategory({required this.entry, this.category}); } class CategoryWithCount { // can be null, in which case we count how many entries don't have a category final Category? category; final int count; // amount of entries in this category CategoryWithCount(this.category, this.count); }