home / skills / madteacher / mad-agents-skills / flutter-drift

flutter-drift skill

/flutter-drift

This skill guides you to implement a resilient local SQLite store in Flutter with drift, enabling type-safe queries, reactive streams, and smooth migrations.

npx playbooks add skill madteacher/mad-agents-skills --skill flutter-drift

Review the files below or copy the command above to add this skill to your agents.

Files (8)
SKILL.md
6.2 KB
---
name: flutter-drift
description: Complete guide for using drift database library in Flutter applications. Use when building Flutter apps that need local SQLite database storage with type-safe queries, reactive streams, migrations, and efficient CRUD operations. Includes setup with drift_flutter package, StreamBuilder integration, Provider/Riverpod patterns, and Flutter-specific database management for mobile, web, and desktop platforms.
---

# Flutter Drift

Comprehensive guide for using drift database library in Flutter applications.

## Overview

Flutter Drift skill provides complete guidance for implementing persistent local storage in Flutter apps using the drift library. Drift is a reactive persistence library for Flutter built on SQLite, offering type-safe queries, auto-updating streams, schema migrations, and cross-platform support.

## Quick Start

Add dependencies to `pubspec.yaml`:

```yaml
dependencies:
  drift: ^2.30.0
  drift_flutter: ^0.2.8
  path_provider: ^2.1.5

dev_dependencies:
  drift_dev: ^2.30.0
  build_runner: ^2.10.4
```

Define database:

```dart
@DriftDatabase(tables: [TodoItems])
class AppDatabase extends _$AppDatabase {
  AppDatabase([QueryExecutor? e])
      : super(
          e ??
              driftDatabase(
                name: 'app_db',
                native: const DriftNativeOptions(
                  databaseDirectory: getApplicationSupportDirectory,
                ),
                web: DriftWebOptions(
                  sqlite3Wasm: Uri.parse('sqlite3.wasm'),
                  driftWorker: Uri.parse('drift_worker.js'),
                ),
              ),
        );

  @override
  int get schemaVersion => 1;
}
```

Run code generator:

```bash
dart run build_runner build
```

## Reference Files

See detailed documentation for each topic:

- [setup.md](references/setup.md) - Flutter-specific setup with drift_flutter
- [tables.md](references/tables.md) - Table definitions, columns, constraints
- [queries.md](references/queries.md) - SELECT, WHERE, JOIN, aggregations
- [writes.md](references/writes.md) - INSERT, UPDATE, DELETE, transactions
- [streams.md](references/streams.md) - Reactive stream queries
- [migrations.md](references/migrations.md) - Database schema migrations
- [flutter-ui.md](references/flutter-ui.md) - Flutter UI integration patterns

## Common Patterns

### Reactive Todo List with StreamBuilder

```dart
class TodoList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final database = Provider.of<AppDatabase>(context);

    return StreamBuilder<List<TodoItem>>(
      stream: select(database.todoItems).watch(),
      builder: (context, snapshot) {
        final todos = snapshot.data ?? [];
        return ListView.builder(
          itemCount: todos.length,
          itemBuilder: (context, index) {
            final todo = todos[index];
            return ListTile(
              title: Text(todo.title),
              trailing: Checkbox(
                value: todo.isCompleted,
                onChanged: (value) {
                  database.update(database.todoItems).replace(
                    TodoItem(
                      id: todo.id,
                      title: todo.title,
                      isCompleted: value ?? false,
                    ),
                  );
                },
              ),
            );
          },
        );
      },
    );
  }
}
```

### Add Item with Form

```dart
Future<void> showAddTodoDialog(BuildContext context) async {
  final controller = TextEditingController();
  final database = Provider.of<AppDatabase>(context);

  await showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: const Text('Add Todo'),
        content: TextField(
          controller: controller,
          decoration: const InputDecoration(labelText: 'Title'),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('Cancel'),
          ),
          TextButton(
            onPressed: () async {
              if (controller.text.isNotEmpty) {
                await database.into(database.todoItems).insert(
                  TodoItemsCompanion.insert(title: controller.text),
                );
                if (context.mounted) {
                  Navigator.pop(context);
                }
              }
            },
            child: const Text('Add'),
          ),
        ],
      );
    },
  );

  controller.dispose();
}
```

### Provider Setup

```dart
final databaseProvider = Provider<AppDatabase>((ref) {
  final database = AppDatabase();
  ref.onDispose(database.close);
  return database;
});
```

### Database Migration

```dart
@override
MigrationStrategy get migration {
  return MigrationStrategy(
      onUpgrade: stepByStep(
        from1To2: (m, schema) async {
          await m.addColumn(schema.todoItems, schema.todoItems.dueDate);
        },
      ),
    );
}
```

## Platform-Specific Setup

### Mobile (Android/iOS/macOS/Windows/Linux)

Uses `drift_flutter` with `getApplicationSupportDirectory`.

### Web

Place `sqlite3.wasm` and `drift_worker.js` in `web/` folder.

### Isolate Sharing

```dart
AppDatabase.defaults(): super(
  driftDatabase(
    name: 'app_db',
    native: DriftNativeOptions(
      shareAcrossIsolates: true,
    ),
  ),
);
```

## Testing

Use in-memory database for tests:

```dart
AppDatabase createTestDatabase() {
  return AppDatabase(NativeDatabase.memory());
}
```

## Best Practices

1. **Use drift_flutter** for easy database setup across platforms
2. **StreamBuilder** for reactive UI updates
3. **Provider/Riverpod** for database access management
4. **Close database** on app/widget dispose
5. **Use migrations** when schema changes
6. **Index columns** used in WHERE clauses
7. **Limit stream query size** for performance
8. **Use transactions** for multi-step operations
9. **Debounce user input** for search/filter
10. **Handle loading/error states** in UI

## Troubleshooting

### Build Fails

```bash
dart run build_runner clean
dart run build_runner build --delete-conflicting-outputs
```

### Migration Errors

```bash
dart run drift_dev schema validate
dart run drift_dev make-migrations
```

### Stream Not Updating

Ensure operations go through drift APIs, not raw SQLite.

Overview

This skill is a complete guide for using the Drift database library in Flutter applications. It focuses on type-safe SQLite access, reactive streams, migrations, and platform-aware setup for mobile, web, and desktop. The content emphasizes practical patterns for CRUD, StreamBuilder integration, and state management with Provider or Riverpod.

How this skill works

The guide explains how to configure drift_flutter, run the code generator, and define a DriftDatabase with tables and schemaVersion. It shows how to perform inserts, updates, deletes, and transactions using generated DAOs and companions, and how to expose live queries as Streams for reactive UIs. Platform-specific advice covers native options for mobile/desktop, wasm and worker files for web, and isolate sharing and in-memory databases for testing.

When to use it

  • Building Flutter apps that require local persistent storage with SQLite
  • Need type-safe, generated queries and compile-time schema checks
  • UI must react to database changes using streams
  • Managing schema migrations across app versions
  • Testing data logic with in-memory databases

Best practices

  • Use drift_flutter for cross-platform database initialization
  • Expose the database via Provider or Riverpod and close it on dispose
  • Use StreamBuilder or stream-based state management for live UI updates
  • Define explicit migrations and run schema validation during development
  • Wrap multi-step DB changes in transactions for consistency
  • Index frequently filtered columns and limit stream query sizes for performance

Example use cases

  • Reactive todo lists backed by a Drift database and StreamBuilder
  • Form-driven inserts with companions and validation dialogs
  • Background isolates sharing a single database instance across threads
  • Web deployment using sqlite3.wasm and drift_worker.js in the web/ folder
  • Unit tests using an in-memory NativeDatabase for fast, isolated tests

FAQ

How do I generate the Drift code after defining tables?

Run the build runner: dart run build_runner build (use --delete-conflicting-outputs if needed).

What files are required for web support?

Place sqlite3.wasm and drift_worker.js in your web/ folder and configure DriftWebOptions with their URIs.

How do I test database logic?

Create the database with NativeDatabase.memory() or pass an in-memory QueryExecutor and close it after each test.