
In app design, it is necessary to permanently store specific data.
In such cases, a local DB is designed within the user’s smartphone.
Previously, I introduced Isar, but my personal evaluation is that Isar has a low update frequency, which is concerning.
Therefore, this time, I will introduce Drift, a high-speed RDB that can compete with Isar.
When Do You Need a Local DB in the First Place?
Normally, variables and states used in an app are lost when the app is closed,
but some apps have data like memos or chat histories that you want to use the next time the app is launched.
To save such data, a local DB is used.
What is Drift?
Drift is a powerful local database library for Flutter and Dart.
It is based on SQLite and offers a type-safe query builder, reactive streams,
and intuitive data manipulation. Formerly known as Moor.
Key Features of Drift
By saving data in a local DB, you can access it the next time the app is launched.
Drift has the following features:
- Type-Safe Queries
Drift leverages Dart’s type system to detect query errors at compile time.
It’s great to catch errors during coding! - Reactive Streams
It can detect changes in the database in real-time and automatically update the UI. - Scalability and Customizability
Drift is highly scalable and allows you to easily add custom functions and triggers.
Handling complex queries and transactions is straightforward, providing high flexibility! - Comprehensive Documentation
Drift is supported by detailed documentation and an active community,
so you can likely do most things by referring to the documentation!?
Official Documentation: https://drift.simonbinder.eu/setup/ - Active Update Frequency
Drift is frequently updated, with version upgrades every two weeks to a month.
This might be because Stream Inc. sponsors Drift, keeping it active.
How to Use Drift
Here, I will introduce how to use Drift.
Setting Up the Environment
Add the necessary packages to the “pubspec.yaml”.
Please use the versions that suit your environment.
dependencies:
~ Other existing packages ~
drift: ^2.23.1
sqlite3_flutter_libs: ^0.5.28
dev_dependencies:
build_runner: ^2.4.14
drift_dev: ^2.23.1
- drift: Core library for Drift
- sqlite3_flutter_libs: Additional library to use SQLite3 in Flutter
- build_runner: Tool for Dart code generation
- drift_dev: Drift’s code generation tool
Defining the Database and Tables
First of all, since Drift is an RDB, you need to define the database and tables.
This time, the files are separated as follows, but you can also define tables and the database in a single file.
- Table Definition File: todo_tbl.dart
- Database Definition File: app_db.dart
- Auto-Generated File: app_db.g.dart
Table Definition
Define the tables and set the data to be managed.
For example, let’s define a table to manage ToDo information: ToDos.
part of 'app_db.dart';
class Todos extends Table {
IntColumn get id => integer().autoIncrement()(); // autoIncrement() for automatic numbering
TextColumn get title => text().withLength(min: 1, max: 50)();
BoolColumn get completed => boolean().withDefault(Constant(false))();
}
By adding an “s” at the end of the table name, the record name automatically excludes the “s”.
In the above example, the table name is ToDos, and the record name is ToDo.
Defining the Database
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
import 'dart:io';
part 'todo_tbl.dart'; // File where the table is defined above
part 'app_db.g.dart'; // File automatically generated by the generator
@DriftDatabase(tables: [Todos]) // If there are multiple tables, specify them separated by commas like [ToDos, Users]
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 1; // Schema version
// Operations
Future<List<ToDo>> getAllTodos() => select(todos).get(); // Retrieve all records
Future<List<ToDo>> fetchTodos(String word) async {
return await (select(todos)..where((tbl) => tbl.title.equals(word))).get();
}
Future insertTodo(TodosCompanion todo) => into(todos).insert(todo); // Insert a record
Future updateTodo(Todo todo) => update(todos).replace(todo); // Update a record
Future deleteTodo(int id) =>
(delete(todos)..where((tbl) => tbl.id.equals(id))).go(); // Delete a record
Stream<List<ToDo>> watchAllTodos() => select(todos).watch(); // (Reference) Watch records
}
// Location to store the actual data of the local DB
LazyDatabase _openConnection() {
return LazyDatabase(() async {
final dbFolder = await getApplicationDocumentsDirectory();
final file = File(p.join(dbFolder.path, 'db.sqlite'));
return NativeDatabase(file);
});
}
The imported “app_db.g.dart” does not exist at this point, so an error will occur.
By running the following command in the terminal, “app_db.g.dart” will be created.
flutter pub run build_runner build --delete-conflicting-outputs
Basic Usage of Drift
Here, I will introduce client-side processing for performing database operations using Drift.
Basically, you just call the operations set in the database above.
Fetching Data
import 'app_db.dart';
final db = AppDatabase();
Future<List<ToDo>> fetchAllTodos() async {
return await db.getAllTodos();
}
Conditional Data Retrieval
import 'app_db.dart';
final db = AppDatabase();
Future<List<ToDo>> fetchTodos(String word) async {
return await db.fetchTodos(word);
}
Inserting Data
import 'app_db.dart';
final db = AppDatabase();
Future<void> addTodo(String title) async {
await db.insertTodo(TodosCompanion(
title: Value(title),
));
}
Updating Data
import 'app_db.dart';
final db = AppDatabase();
Future<void> toggleTodoStatus(Todo todo) async {
final updatedTodo = todo.copyWith(completed: !todo.completed);
await db.updateTodo(updatedTodo);
}
Deleting Data
import 'app_db.dart';
final db = AppDatabase();
Future<void> deleteTodoItem(int id) async {
await db.deleteTodo(id);
}
Domain Models and Mapping for Data
Usually, the data handled within an app is not simple and is defined as a domain model.
In such cases, data conversion between the domain model and the database model is necessary.
Defining the Domain Model
Assume that the ToDo
class in the domain model is as follows.
/// Domain Model: ToDo
class ToDo {
final int? id;
final String title;
final bool isCompleted;
ToDo({
this.id,
required this.title,
this.isCompleted = false,
});
}
Conversion Between Domain and Database Models
Using extension methods, implement the conversion process between the domain model and the database model.
/// Conversion from Domain Model to Drift's Database Model
extension ToDoToDatabase on ToDo {
TodosCompanion toInsertCompanion() {
return TodosCompanion.insert(
// id is automatically set during insert
title: title,
completed: completed,
);
}
TodosCompanion toUpdateCompanion() {
return TodosCompanion(
id: Value(id),
title: Value(title),
completed: Value(isCompleted),
);
}
}
/// Conversion from Drift's Database Model to Domain Model
extension DatabaseToDo on Todo {
ToDo toDomain() {
return ToDo(
id: id,
title: title,
isCompleted: completed,
);
}
}
Data Operations Utilizing Mapping
As an example, I will introduce data operations using the above conversion process.
import 'app_db.dart';
final db = AppDatabase();
// Insert ToDo
Future<void> addToDoItem(ToDo toDo) async {
await db.insertTodo(toDo.toInsertCompanion());
}
// Retrieve All ToDos
Future<List<ToDo>> getToDoItems() async {
final dbTodos = await db.getAllTodos();
return dbTodos.map((dbTodo) => dbTodo.toDomain()).toList();
}
Cautions When Using Drift
Since Drift is an RDB, version management is necessary when changing table definitions or schemas.
Additionally, when adding data items, migration processes must be written for each version.
For more details, please refer to the official documentation.
Official Documentation: https://drift.simonbinder.eu/Migrations/step_by_step/?h=migration
Conclusion
The processing speed of Drift was not much different from Isar in actual device experience.
Due to the high update frequency of the package, it is easy to adjust dependencies with other packages, making it user-friendly.
I believe Drift is a good alternative to Isar.
I encourage everyone to try developing with Drift!