Development

Building Offline-First Flutter Apps with Drift

Muhammad Shakil Muhammad Shakil
Feb 23, 2026
5 min read
Building Offline-First Flutter Apps with Drift
Back to Blog

In today's mobile-first world, offline functionality is no longer a luxury—it's a necessity. With over 60% of mobile users experiencing connectivity issues daily, apps that fail gracefully offline risk losing users permanently. That's where Flutter offline-first apps with Drift come in. Drift (formerly known as Moor) is a powerful Flutter local database solution that enables developers to build robust offline-first applications with ease. In this comprehensive guide, we'll explore everything from basic setup to advanced optimization techniques, complete with practical code examples and real-world scenarios.

TL;DR: Key Takeaways for Building Offline-First Flutter Apps with Drift

  1. Drift provides a type-safe, reactive SQLite wrapper for Flutter
  2. Offline-first architecture ensures seamless user experience regardless of network conditions
  3. Drift's migration system makes database schema changes painless
  4. Combine Drift with Riverpod for optimal state management
  5. Use background sync to maintain data consistency across devices
  6. Implement conflict resolution strategies for multi-device scenarios
  7. Optimize performance with indexing and batch operations

What is Drift and Why Use It for Flutter Offline Apps?

Drift is a feature-rich Flutter local database solution that builds upon SQLite. It provides a type-safe API for database interactions, making it easier to write maintainable code while preventing runtime errors. Unlike traditional SQLite wrappers, Drift offers:

Key Features of Drift

Why Drift for Offline-First Apps?

Building offline-first apps requires a robust local storage solution. Drift excels in this area because:

// Example: Basic Drift setup
        import 'package:drift/drift.dart';
        import 'package:drift/native.dart';

        part 'database.g.dart';

        class Tasks extends Table {
          IntColumn get id => integer().autoIncrement()();
          TextColumn get name => text().withLength(min: 1, max: 50)();
          BoolColumn get completed => boolean().withDefault(Constant(false))();
        }

        @DriftDatabase(tables: [Tasks])
        class AppDatabase extends _$AppDatabase {
          AppDatabase() : super(NativeDatabase.memory());

          @override
          int get schemaVersion => 1;
        }

💡 Pro Tip

Always use part 'database.g.dart'; to generate type-safe database code automatically.

Setting Up Drift for Offline-First Architecture

Creating an offline-first architecture with Drift involves several key components:

Database Initialization

// Initialize Drift database
        final db = AppDatabase();

        // Accessing the database
        final tasks = await db.select(db.tasks).get();

Handling Offline Data Storage

Implement a data persistence strategy:

// Example: Saving data locally
        Future saveTask(String taskName) async {
          await db.into(db.tasks).insert(TasksCompanion.insert(name: taskName));
        }

⚠️ Warning

Always test your offline-first implementation in airplane mode to simulate real-world conditions.

Drift vs Other Flutter Local Database Solutions

When choosing a local database for Flutter offline apps, consider these alternatives:

Comparison Table

When to Choose Drift

Drift is ideal when you need:

Common Mistakes When Building Flutter Offline Apps

Even experienced developers can stumble when implementing offline-first strategies:

Mistake 1: Not Handling Conflicts

// ❌ Wrong: Overwriting data without conflict resolution
        await db.update(db.tasks).write(task);

        // ✅ Right: Implementing conflict resolution
        await db.update(db.tasks).write(task.copyWith(
          updatedAt: DateTime.now(),
        ));

Mistake 2: Ignoring Schema Migrations

// ❌ Wrong: Hardcoding schema version
        @override
        int get schemaVersion => 1;

        // ✅ Right: Implementing proper migrations
        MigrationStrategy get migration => MigrationStrategy(
          onCreate: (Migrator m) {
            return m.createAll();
          },
          onUpgrade: (Migrator m, int from, int to) async {
            if (from == 1) {
              await m.addColumn(db.tasks, db.tasks.priority);
            }
          },
        );

Performance Optimization Techniques

Optimize your Flutter offline app's performance with these strategies:

Indexing for Faster Queries

// Create indexes for frequently queried columns
        class Tasks extends Table {
          IntColumn get id => integer().autoIncrement()();
          TextColumn get name => text().withLength(min: 1, max: 50)();
          BoolColumn get completed => boolean().withDefault(Constant(false))();

          @override
          Set<Column> get primaryKey => {id};

          @override
          List<Index> get indexes => [
            Index([name]),
          ];
        }

Batch Operations

// Use batch operations for bulk inserts
        Future insertTasks(List<Task> tasks) async {
          await db.batch((batch) {
            batch.insertAll(db.tasks, tasks);
          });
        }

Real-World Implementation: Building a Task Manager

Let's walk through building a task manager app with offline-first capabilities:

Step 1: Define Database Schema

class Tasks extends Table {
          IntColumn get id => integer().autoIncrement()();
          TextColumn get name => text().withLength(min: 1, max: 50)();
          BoolColumn get completed => boolean().withDefault(Constant(false))();
          DateTimeColumn get createdAt => dateTime().withDefault(Constant(DateTime.now()))();
        }

Step 2: Implement Sync Logic

Future syncWithServer() async {
          final localTasks = await db.select(db.tasks).get();
          final serverTasks = await fetchTasksFromServer();

          // Implement conflict resolution
          final mergedTasks = [...localTasks, ...serverTasks]
            .fold<Map<int, Task>>({}, (map, task) {
              map[task.id] = task;
              return map;
            }).values.toList();

          await db.transaction(() async {
            await db.delete(db.tasks).go();
            await db.batch((batch) {
              batch.insertAll(db.tasks, mergedTasks);
            });
          });
        }

Summary: Building Robust Flutter Offline-First Apps with Drift

Building offline-first Flutter apps with Drift provides a powerful solution for modern mobile applications. By leveraging Drift's type-safe queries, reactive streams, and migration support, developers can create apps that work seamlessly across various network conditions. Remember to:

🚀 What's Next?

Explore advanced topics like state management with Riverpod and performance optimization techniques to take your Flutter offline-first apps to the next level.

Frequently Asked Questions

What is Drift in Flutter for offline-first apps?

Drift is a reactive persistence library for Flutter that simplifies database management and supports offline-first app development. It allows apps to store data locally using SQLite and synchronize it when online.

How does Drift enable offline-first functionality in Flutter apps?

Drift uses SQLite as its underlying database, enabling local data storage and offline access. It provides tools for reactive queries, migrations, and seamless synchronization when the app regains internet connectivity.

Can Drift be used with other state management solutions in Flutter?

Yes, Drift can be integrated with popular state management solutions like Provider, Riverpod, or Bloc. It provides reactive streams that work well with these tools to manage app state efficiently.

Is Drift better than Hive for offline-first Flutter apps?

Drift is better for complex relational data due to its SQLite foundation, while Hive excels in simplicity and speed for key-value storage. The choice depends on your app's data structure and requirements.

How to set up Drift in a Flutter project?

Add Drift to your `pubspec.yaml` file (`drift: ^2.10.0`), create a database class extending `GeneratedDatabase`, and define tables using Dart annotations. Use `drift_dev` for code generation to automate boilerplate code.

Which Flutter apps benefit most from using Drift?

Flutter apps that require complex local data storage, offline functionality, and synchronization, such as productivity tools, note-taking apps, or e-commerce platforms, benefit most from Drift's SQLite-based architecture.

Share this article:

Have an App Idea?

Let our team turn your vision into reality with Flutter.