Migrating Local Databases in Flutter: From Realm to Drift (and Beyond)
The Flutter news you actually need
No spam, ever. Unsubscribe in one click.
So your Flutter app’s local database has become a liability. Maybe it’s crashing randomly, or the package you chose has stopped receiving updates. You’re not alone—many of us have been there. The initial prototype often uses the most convenient database, but as your app grows and you need stability, a migration becomes inevitable.
A common path is moving from an unmaintained or unstable NoSQL solution like Realm to a robust, SQLite-based library such as Drift. This migration isn’t just about swapping packages; it’s a strategic move towards long-term data integrity and reliability.
Let’s break down how to approach this migration methodically.
Step 1: Audit and Understand Your Current Data
Before writing a single line of new code, you need a complete map of your existing data. What objects are stored? What are their relationships? Export a sample of your Realm database and document the schema.
For example, if you had a Session object in Realm:
// Old Realm Model (Conceptual)
class OldSession {
@PrimaryKey()
int? id;
String mantraName;
int count;
DateTime date;
}
Step 2: Design the New Drift Schema
With Drift, you define your schema using Dart (or SQL). Design your new tables, keeping future needs in mind.
Here’s how you might define the equivalent sessions table:
import 'package:drift/drift.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'dart:io';
part 'database.g.dart';
class Sessions extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get mantraName => text()();
IntColumn get count => integer()();
DateTimeColumn get date => dateTime()();
}
@DriftDatabase(tables: [Sessions])
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 1;
}
LazyDatabase _openConnection() {
return LazyDatabase(() async {
final dbFolder = await getApplicationDocumentsDirectory();
final file = File(p.join(dbFolder.path, 'app_db.sqlite'));
return NativeDatabase(file);
});
}
Step 3: The Migration Script: A Critical Phase
This is the heart of the process. You must write a one-time script that reads all data from the old Realm database and writes it into the new Drift database. Do this during your app’s first launch after the update.
Create a dedicated migration service:
class DatabaseMigrator {
final Realm oldRealmInstance;
final AppDatabase newDriftDatabase;
Future<void> migrateAllData() async {
// 1. Read all old objects
final oldSessions = oldRealmInstance.all<OldSession>();
// 2. Transform and insert into new database
for (final oldSession in oldSessions) {
await newDriftDatabase.into(newDriftDatabase.sessions).insert(
SessionsCompanion.insert(
mantraName: Value(oldSession.mantraName),
count: Value(oldSession.count),
date: Value(oldSession.date),
),
);
}
// 3. Close the old realm
oldRealmInstance.close();
}
}
Crucial: Wrap this in extensive error handling and logging.
Step 4: Update Your App’s Architecture
Your old data layer was tightly coupled to Realm. Now’s the time to abstract it. Implement a repository pattern that uses your new AppDatabase.
abstract class SessionRepository {
Future<List<Session>> getAllSessions();
Future<void> addSession(Session session);
}
class DriftSessionRepository implements SessionRepository {
final AppDatabase db;
DriftSessionRepository(this.db);
@override
Future<List<Session>> getAllSessions() async {
final rows = await db.select(db.sessions).get();
return rows.map((row) => Session.fromRow(row)).toList();
}
// ... other methods
}
Step 5: Planning for the Cloud
Once you have a stable local SQLite foundation, adding cloud sync becomes a more manageable problem.
Final Advice
- Test Relentlessly: Create a comprehensive suite of unit and integration tests for your migration script and new data layer.
- Communicate with Users: For a production app, consider a phased rollout.
- Embrace the Refactor: This migration is a significant investment. Use it as an opportunity to clean up related technical debt in your data layer.
Migrating your database is a complex, high-stakes task, but moving to a well-maintained, standards-based solution like Drift provides the rock-solid foundation your app needs to grow.
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.