← 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 Flutter App Size Optimization: Strategies to Shrink Your APK/IPA Below 10MB

Flutter App Size Optimization: Strategies to Shrink Your APK/IPA Below 10MB

Flutter apps are often criticized for their larger-than-native binary sizes. This post will dive into practical, actionable strategies to significantly reduce your Flutter app's footprint, including build configurations, asset optimization, and dependency management, demonstrating how to achieve app sizes under 10MB.

Cover image for Mastering In-App Subscriptions in Flutter: A Guide to Avoiding Common Pitfalls

Mastering In-App Subscriptions in Flutter: A Guide to Avoiding Common Pitfalls

Implementing in-app subscriptions in Flutter can be surprisingly complex, especially when dealing with cross-platform nuances, receipt validation, and managing different store configurations. This post will delve into the common challenges developers face, compare solutions like RevenueCat with custom implementations, and provide best practices for setting up and maintaining a robust subscription system.

Cover image for Taming Android Emulator RAM: A Developer's Guide to Reducing Memory Usage for Flutter

Taming Android Emulator RAM: A Developer's Guide to Reducing Memory Usage for Flutter

Android emulators are notorious for hogging RAM, often pushing developer machines into swap and slowing down Flutter development. This post will explore the reasons behind high emulator memory consumption and provide practical, actionable strategies and tools (including a custom-built solution) to significantly reduce RAM usage, ensuring a smoother development experience without sacrificing functionality.