Drift Database Documentation

website·Indexed 34 days ago

https://drift.simonbinder.eu

A relational database library for Dart and Flutter, originally designed as a client-side wrapper for SQLite. It provides a fluent Dart API for queries, support for .drift SQL files, and compatibility with PostgreSQL, libSQL, and Turso. Features include auto-updating stream queries, type converters, modular code generation, and cross-platform support for mobile and desktop.

Tokens
49.1K
Snippets
242
Records
308
Agent score
67%

What's inside Drift

  1. Overview of backend synchronization in drift

    drift is primarily a relational database access package. While it does not provide automatic synchronization between clients and backends out-of-the-box, it can be integrated with various sync solutions. In fullstack Dart applications, drift can be used on both the server (with PostgreSQL) and the client (with SQLite). To share schema definitions between these different database systems, the DialectAwareSqlType utility can be used.
  2. Overview of Drift reactive persistence library

    Drift is a reactive persistence library for relational data in Dart and Flutter applications. It acts as a layer on top of database libraries such as sqlite3 and sqflite to provide the following features:

    • Type Safety: Automatically converts database rows into Dart objects, eliminating the need to manually parse List<Map<String, dynamic>>.
    • Stream Queries: Allows queries to be converted into auto-updating streams that emit new data whenever the underlying database changes.
    • Fluent Queries: Generates Dart methods and classes for writing queries without writing raw SQL (e.g., using select(users).watch()).
    • Type-safe SQL: Includes a built-in SQL parser and analyzer that validates queries at compile time and generates corresponding Dart code for the results.
    • Migration Utilities: Simplifies schema updates with utility functions like .createAllTables(), reducing the need for manual CREATE TABLE statements.
    • Additional Capabilities: Supports data validation before insertion, transactions, Data Access Objects (DAOs), and efficient batched inserts.
  3. Prevent main isolate blocking with NativeDatabase.createInBackground

    Because sqlite3 is a synchronous C library, database operations on the main isolate can cause UI lag. The recommended way to avoid this is using NativeDatabase.createInBackground, which automatically spawns a background isolate managed by drift to execute SQL statements without requiring manual isolate setup.
  4. Implement drift in a Flutter app using a comprehensive example

    For developers looking to integrate drift into a Flutter application using a modern architecture and following best practices, a comprehensive cross-platform example is available in the official drift repository. This example serves as a reference implementation for structuring a Flutter app with drift.
    Refer to the official example app on GitHub: https://github.com/simolus3/drift/tree/develop/examples/app
  5. Understand generated row and companion classes

    For every table defined in Drift, two classes are generated:

    1. Row Class: Represents a full row of the table. It is used for query results and provides type-safe access to columns. It includes built-in equality, hashing, and a copyWith method.
    2. Companion Class: Represents a partial row. It is primarily used for inserts and updates, especially when some values (like auto-incrementing primary keys) are not yet known or when only specific columns should be updated without affecting others.
  6. Set up Native Drift for cross-platform apps

    The drift/native.dart library uses the sqlite3 package to execute queries. Starting from drift version 2.32.0, all native Dart and Flutter platforms are supported without additional setup or dependencies (e.g., sqlite3_flutter_libs is no longer required).
  7. Manage a singleton database instance in Flutter

    It is recommended to maintain a single (singleton) instance of your MyDatabase class. Depending on your architecture, you can achieve this using a global variable, the provider package, GetX, or a dependency injection framework like get_it or kiwi.
    // Option 1: Vanilla Flutter (Global Variable)
    late MyDatabase database;
    
    void main() {
      database = MyDatabase();
      runApp(MyFlutterApp());
    }
    
    // Option 2: Using the provider package
    void main() {
      runApp(
        Provider<MyDatabase>(
          create: (context) => MyDatabase(),
          child: MyFlutterApp(),
          dispose: (context, db) => db.close(),
       ),
      );
    }
    // Access via: Provider.of<MyDatabase>(context)
    
    // Option 3: Using GetX
    void main() {
      Get.put(MyDatabase());
      runApp(MyFlutterApp());
    }
    // Access via: Get.find<MyDatabase>().your_method
  8. Manage database migrations with the Migrator API

    The Migrator class is provided as a parameter in migration callbacks. It allows developers to create, drop, and alter schema elements. For operations not covered by the Migrator API, customStatement() can be used to run raw SQL.
  9. Trace and log database operations using QueryInterceptor

    The QueryInterceptor API allows developers to intercept and log details about database operations, such as execution time, success/failure status, and the specific SQL statements being run. This is more powerful than the basic logStatements option and can be used for performance monitoring, retrying failed statements, or sending statistics to external services.
    class LogInterceptor extends QueryInterceptor {
      Future<T> _run<T>(
        String description,
        FutureOr<T> Function() operation,
      ) async {
        final stopwatch = Stopwatch()..start();
        print('Running $description');
    
        try {
          final result = await operation();
          print(' => succeeded after ${stopwatch.elapsedMilliseconds}ms');
          return result;
        } on Object catch (e) {
          print(' => failed after ${stopwatch.elapsedMilliseconds}ms ($e)');
          rethrow;
        }
      }
    
      @override
      TransactionExecutor beginTransaction(QueryExecutor parent) {
        print('begin');
        return super.beginTransaction(parent);
      }
    
      @override
      Future<void> commitTransaction(TransactionExecutor inner) {
        return _run('commit', () => inner.send());
      }
    
      @override
      Future<void> rollbackTransaction(TransactionExecutor inner) {
        return _run('rollback', () => inner.rollback());
      }
    
      @override
      Future<int> runInsert(
        QueryExecutor executor,
        String statement,
        List<Object?> args,
      ) {
        return _run('$statement with $args', () => executor.runInsert(statement, args));
      }
    
      @override
      Future<List<Map<String, Object?>>> runSelect(
        QueryExecutor executor,
        String statement,
        List<Object?> args,
      ) {
        return _run('$statement with $args', () => executor.runSelect(statement, args));
      }
      
      // Similarly override runUpdate, runDelete, runCustom, and runBatched
    }
  10. Troubleshoot Android Out-of-Memory errors with NativeDatabase

    When using package:drift/native.dart on Android, very complex queries may trigger out-of-memory errors because the default temporary directory is unavailable. To resolve this, you must explicitly inform sqlite3 about the correct directory to store temporary data.
  11. Reference community-led drift implementations

    For real-world use cases and complex architectures, refer to these community open-source projects using drift:

    • Clean Architecture: A Todo app demonstrating drift within a complex clean architecture. View Repository
    • BingeTube: A production-grade app for watching YouTube channels chronologically. View Repository
    • HackerNews Reader: An app that uses drift to persist a list of favorite articles. View Repository
    • Hotel Booking App: Demonstrates various database relationships using GetX. View Repository
  12. Understand Drift DateTime storage modes

    Drift provides two modes for storing DateTime values in SQL, configurable via the store_date_time_values_as_text build option:

    1. Unix Timestamp (Default): Stores values as an SQL INTEGER (seconds). When retrieved, Drift returns a non-UTC value; original UTC information is lost.
    2. ISO 8601 String: Stores values as text using DateTime.toIso8601String().
      • Values ending in Z are returned as UTC.
      • Values with a UTC offset (e.g., +02:00) are parsed and converted via toLocal() to return a local value.
      • Values without a suffix are treated as UTC (matching sqlite3's datetime function behavior).

    Regardless of the mode, Drift's built-in date and time functions handle the conversion internally (using unixepoch for timestamps and julianday for text comparisons).