Choosing the Right Local Database for Flutter: Beyond Isar's End-of-Life
The Flutter news you actually need
No spam, ever. Unsubscribe in one click.
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
Flutter for High-Performance Desktop: Is it Ready for CAD, Image Processing, and Complex GUIs?
Developers are curious about Flutter's capabilities beyond typical business apps, especially for demanding desktop applications like CAD/CAM or image/video processing. This post will explore Flutter's suitability for high-performance, viewport-based desktop GUIs, discussing Dart's memory model, the 60fps update loop, and real-world examples to gauge its readiness for 'serious' complex software.
Debugging Flutter Web Navigation: Solving the Deep Link Refresh Bug
Flutter web applications often suffer from a frustrating 'deep link refresh bug' where refreshing the browser on a nested route (e.g., /home/details) bounces the user back to the root or an incorrect path. This post will diagnose the common causes of this issue, explain how Flutter's router handles web URLs, and provide practical solutions and best practices for building robust, refresh-proof navigation in your Flutter web apps.
Mastering Internationalization in Flutter: Centralized Strings for Scalable Apps
As Flutter applications grow, managing strings for multiple languages or just keeping text consistent becomes a challenge. This post will guide developers through effective strategies for centralizing strings, implementing robust internationalization (i18n) and localization (l10n), and leveraging tools to streamline the process for small to large-scale projects.