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's Hidden Gem: Building Powerful Linux Desktop Apps with Ease
Flutter is gaining traction as a robust framework for Linux desktop development, offering a smoother experience compared to traditional toolkits like GTK and Qt. This post will explore why Flutter excels for Linux apps, highlight its advantages, and provide practical insights for developers looking to leverage Flutter for their desktop projects.
Simplifying Flutter Desktop Deployment: Signing and Distribution for Windows
Deploying Flutter desktop apps on Windows can be challenging, especially regarding code signing and preventing Windows from blocking installations. This post will guide developers through the process of signing their Flutter desktop applications, exploring alternatives to the Microsoft Store like creating executable installers, and covering the necessary steps to ensure a smooth user experience.
Mastering Flutter Tooling: Streamlining SDK Management and Installation on Windows
Developers frequently voice frustrations about Flutter SDK version management and installation processes, particularly on Windows. This post will explore best practices for managing multiple Flutter versions using tools like FVM, discuss integrating Flutter with package managers like Winget for a smoother Windows setup, and offer strategies to ensure a consistent development environment across projects and teams.