← Back to posts Cover image for Choosing the Right Local Database for Flutter: Beyond Isar's End-of-Life

Choosing the Right Local Database for Flutter: Beyond Isar's End-of-Life

· 5 min read
Weekly Digest

The Flutter news you actually need

No spam, ever. Unsubscribe in one click.

Chris
By Chris

So, you built your Flutter app with a snappy local database, your data layer is humming along, and then you get the news: your chosen database, Isar, is reaching its end-of-life. This scenario has left many developers in a tough spot, facing the non-trivial task of migrating their app’s foundational data layer. The good news? The Flutter ecosystem is robust, and there are excellent, well-maintained alternatives ready for you. Let’s move beyond the panic and evaluate two of the strongest contenders: ObjectBox and Drift.

The core lesson here is about sustainability. Choosing a database is a long-term architectural decision. While features and benchmarks are important, the health of the project—active maintenance, a clear roadmap, and community support—is paramount. Your new choice needs to be a dependable foundation for years to come.

Contender 1: ObjectBox – High-Performance NoSQL

ObjectBox is a superfast, NoSQL object database built for high performance on devices. If you loved Isar’s NoSQL approach and its speed, ObjectBox will feel familiar and is arguably its spiritual successor in the Flutter space. It’s maintained by a dedicated company, which bodes well for its future.

Key Features:

  • Blazing Speed: Benchmarks often show it outperforming SQLite for many operations.
  • Object-Oriented: You work directly with your Dart objects; no ORM mapping needed.
  • Live Queries: Get reactive streams that automatically update when the underlying data changes.
  • Strong Relations: Supports To-One, To-Many, and even Many-to-Many relationships.

Integration & Code Example:

First, add the dependencies to your pubspec.yaml:

dependencies:
  objectbox: ^1.8.1
  objectbox_flutter_libs: ^1.8.1

dev_dependencies:
  objectbox_generator: ^1.8.1
  build_runner: ^2.4.0

Define your entity. Notice the @Entity() annotation and the obligatory id field.

// customer.dart
import 'package:objectbox/objectbox.dart';

@Entity()
class Customer {
  int id = 0; // ObjectBox requires an int id field (0 for new objects).
  String name;
  String email;

  Customer({this.id = 0, required this.name, required this.email});
}

Run the code generator to create the necessary binding code:

flutter pub run build_runner build

Using the database is straightforward:

// main.dart (simplified snippet)
import 'package:objectbox/objectbox.dart';

void main() async {
  final store = await openStore(); // Opens the ObjectBox store.
  final customerBox = store.box<Customer>();

  // Put (Insert/Update)
  final newCustomer = Customer(name: 'Alice Smith', email: 'alice@example.com');
  final customerId = customerBox.put(newCustomer);
  print('Saved customer with ID: $customerId');

  // Query
  final query = customerBox.query(Customer_.name.equals('Alice Smith')).build();
  final results = query.find();
  query.close();

  // Live Query (reactive stream)
  final liveQuery = customerBox.query().watch();
  liveQuery.stream.listen((query) {
    print('Customers updated: ${query.find()}');
  });
}

Contender 2: Drift – The Power of SQL, Simplified

Drift (formerly Moor) is a reactive persistence library built on SQLite. It lets you write queries in both typesafe Dart and traditional SQL, giving you the full power and battle-tested reliability of SQLite with a modern, fluent API.

Key Features:

  • SQLite Power: Leverages one of the most deployed databases in the world.
  • Compile-Time Safety: Queries are checked for errors at compile time, not at runtime.
  • Reactive by Design: Easily convert queries into Streams that update when data changes.
  • Flexible: Write pure SQL or use a fluent Dart API.

Integration & Code Example:

Add Drift and its generator to your pubspec.yaml:

dependencies:
  drift: ^2.19.0
  sqlite3_flutter_libs: ^0.5.0
  path_provider: ^2.1.0

dev_dependencies:
  drift_dev: ^2.19.0
  build_runner: ^2.4.0

Define your tables and data access objects:

// database.dart
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
part 'database.g.dart'; // This will be generated

class TaskItems extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get title => text().withLength(min: 1, max: 100)();
  BoolColumn get isCompleted => boolean().withDefault(const Constant(false))();
}

@DriftDatabase(tables: [TaskItems])
class AppDatabase extends _$AppDatabase {
  AppDatabase() : super(_openConnection());
  @override
  int get schemaVersion => 1;
}

LazyDatabase _openConnection() {
  return LazyDatabase(() async {
    final dbFolder = await getApplicationDocumentsDirectory();
    final file = File('${dbFolder.path}/my_app_db.sqlite');
    return NativeDatabase(file);
  });
}

Generate the code with flutter pub run build_runner build. Then, use it in your app:

// Using the database
final db = AppDatabase();

// Insert a task
await db.into(db.taskItems).insert(
      TaskItemsCompanion.insert(title: 'Migrate database'),
    );

// Query all incomplete tasks (reactive stream)
Stream<List<TaskItem>> watchIncompleteTasks() {
  return (db.select(db.taskItems)
        ..where((t) => t.isCompleted.equals(false)))
      .watch();
}

// Use raw SQL for complex operations
Future<void> archiveOldTasks() async {
  await db.customStatement('''
    UPDATE task_items SET title = '[ARCHIVED] ' || title 
    WHERE is_completed = 1 AND date_created < ?
  ''', [DateTime.now().subtract(Duration(days: 30))]);
}

How to Choose?

  • Choose ObjectBox if: Your app demands maximum read/write performance, you prefer a NoSQL/Object-oriented model, and you need built-in reactive live queries without extra setup. It’s excellent for data-heavy, performance-critical applications.
  • Choose Drift if: You value the stability and universality of SQLite, need to execute complex queries or migrations, or your team already has strong SQL knowledge. It offers unparalleled flexibility and control.

Common Migration Mistake to Avoid: Don’t try to write a one-shot converter that moves all data from Isar to your new database on the first run. This can block the UI and cause timeouts. Instead, design a gradual migration strategy. Initialize your new database, and lazily move old data as it’s accessed, or run the migration in a background isolate, showing a progress indicator to the user.

Ultimately, both ObjectBox and Drift are fantastic, production-ready choices backed by active maintenance. Your decision hinges on your app’s specific needs and your team’s preference for NoSQL versus SQL.

This blog is produced with the assistance of AI by a human editor. Learn more

Related Posts

Cover image for Optimizing Flutter UI Performance: Best Practices for Date Formatting and Expensive Operations

Optimizing Flutter UI Performance: Best Practices for Date Formatting and Expensive Operations

Developers often face performance bottlenecks when performing expensive operations like date formatting directly within Flutter's `build` method, especially in fast-scrolling lists. This post will delve into common pitfalls, explain why these operations are costly, and provide practical strategies for optimizing UI performance by caching formatters, using `initState`, and leveraging `compute` for background processing without blocking the UI.

Cover image for Optimizing Your Flutter Dev Setup: IDEs, Simulators, and AI Tools for Peak Productivity

Optimizing Your Flutter Dev Setup: IDEs, Simulators, and AI Tools for Peak Productivity

Flutter developers frequently seek to refine their development environments. This post will dive into popular IDE choices like VS Code and Android Studio, discuss best practices for managing iOS and Android simulators (including in-IDE options), and explore the practical integration of AI tools for code generation and problem-solving to boost overall efficiency.

Cover image for Demystifying Flutter Performance: Practical Strategies for Large-Scale Apps

Demystifying Flutter Performance: Practical Strategies for Large-Scale Apps

Flutter's performance is often blamed for issues in complex applications, but the real culprits are usually architectural decisions, inefficient widget rebuilds, and unoptimized resource handling. This post will dive into common performance bottlenecks in large Flutter apps, providing actionable strategies for profiling, optimizing state management, handling images and network requests efficiently, and leveraging CI/CD for continuous performance monitoring.