
在應用程式的設計中,特定的數據需要永久保存。
此時,我們會設計在用戶手機內建立的本地資料庫(Local DB)。
前幾天介紹了Isar,但個人評價是,Isar的更新頻率較低,令人不安。
因此,這次將介紹不輸給Isar的高速RDB——Drift。
究竟在什麼情況下需要本地資料庫?
通常,應用程式中使用的變數或狀態(State)在應用程式結束時,
數據也會一併消失,但有些應用程式如備忘錄或聊天記錄等,
希望在下次啟動時也能使用這些數據。
為了保存這類數據,我們使用本地資料庫。
什麼是Drift?
Drift是一個強大的本地資料庫庫,適用於Flutter和Dart。
它基於SQLite,提供類型安全的查詢構建器、反應式的數據流,
以及直觀的數據操作。舊名為Moor。
Drift的主要特點
通過將數據保存到本地資料庫中,
下次啟動時也可以使用這些數據。
Drift具有以下特點:
- 類型安全的查詢
Drift利用Dart的類型系統,在編譯時檢測查詢錯誤。
在編碼過程中即可檢測到錯誤,這點非常方便! - 反應式數據流
能夠實時檢測資料庫的變更,並自動更新UI。 - 可擴展性與自定義性
Drift具有高度的可擴展性,可以輕鬆添加自定義函數或觸發器。
複雜的查詢或事務也能輕鬆處理,靈活性高! - 豐富的文檔
Drift由詳細的文檔和活躍的社群支持,
查看文檔即可大部分功能都能掌握!?
官方文檔)https://drift.simonbinder.eu/setup/ - 活躍的更新頻率
Drift的更新頻率較高,最近每兩週到一個月就會有版本更新。
可能是因為Stream公司成為了Drift的贊助商,才使其如此活躍。
Drift的使用方法
以下介紹如何使用Drift。
環境準備
在 “pubspec.yaml” 中添加所需的套件。
版本請根據您自己的環境選擇合適的版本。
dependencies:
~其他現有套件~
drift: ^2.23.1
sqlite3_flutter_libs: ^0.5.28
dev_dependencies:
build_runner: ^2.4.14
drift_dev: ^2.23.1
- drift:Drift的核心庫
- sqlite3_flutter_libs:在Flutter中使用SQLite3的附加庫
- build_runner:用於生成Dart代碼的工具
- drift_dev:Drift的代碼自動生成工具
資料庫與表格的定義
首先,因為Drift是RDB,所以需要定義資料庫和表格。
這次將文件分成如下,但也可以將表格定義和資料庫定義放在同一個文件中。
- 表格定義文件:todo_tbl.dart
- 資料庫定義文件:app_db.dart
- 自動生成文件:app_db.g.dart
表格定義
定義表格並設定要管理的數據。
例如,定義用於管理ToDo資訊的表格:ToDos。
part of 'app_db.dart';
class Todos extends Table {
IntColumn get id => integer().autoIncrement()(); // autoIncrement()為自動編號
TextColumn get title => text().withLength(min: 1, max: 50)();
BoolColumn get completed => boolean().withDefault(Constant(false))();
}
在表格名稱末尾加上”s”,記錄名稱會自動去除”s”。
以上例子中,表格名稱為ToDos,記錄名稱為ToDo。
資料庫的定義
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'; // 上述定義的表格文件
part 'app_db.g.dart'; // 由生成器自動生成的文件
@DriftDatabase(tables: [Todos]) // 若有多個表格,則以逗號分隔,如[ToDos, Users]
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 1; // 架構版本
// 處理
Future<List<Todo>> getAllTodos() => select(todos).get(); // 獲取所有記錄
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); // 插入記錄
Future updateTodo(Todo todo) => update(todos).replace(todo); // 更新記錄
Future deleteTodo(int id) =>
(delete(todos)..where((tbl) => tbl.id.equals(id))).go(); // 刪除記錄
Stream<List<Todo>> watchAllTodos() => select(todos).watch(); // (參考)監視記錄
}
// 本地資料庫的實際數據存放位置
LazyDatabase _openConnection() {
return LazyDatabase(() async {
final dbFolder = await getApplicationDocumentsDirectory();
final file = File(p.join(dbFolder.path, 'db.sqlite'));
return NativeDatabase(file);
});
}
匯入的 “app_db.g.dart” 此時尚不存在,會產生錯誤。
在終端機中執行以下代碼即可生成 “app_db.g.dart”。
flutter pub run build_runner build --delete-conflicting-outputs
Drift的基本使用方法
介紹使用Drift進行資料庫操作的客戶端處理。
基本上,只需調用上述資料庫中設定的處理即可。
獲取數據
import 'app_db.dart';
final db = AppDatabase();
Future<List<Todo>> fetchAllTodos() async {
return await db.getAllTodos();
}
條件獲取數據
import 'app_db.dart';
final db = AppDatabase();
Future<List<Todo>> fetchTodos(String word) async {
return await db.fetchTodos(word);
}
插入數據
import 'app_db.dart';
final db = AppDatabase();
Future<void> addTodo(String title) async {
await db.insertTodo(TodosCompanion(
title: Value(title),
));
}
更新數據
import 'app_db.dart';
final db = AppDatabase();
Future<void> toggleTodoStatus(Todo todo) async {
final updatedTodo = todo.copyWith(completed: !todo.completed);
await db.updateTodo(updatedTodo);
}
刪除數據
import 'app_db.dart';
final db = AppDatabase();
Future<void> deleteTodoItem(int id) async {
await db.deleteTodo(id);
}
數據的領域模型與映射
通常,應用程式中處理的數據並不簡單,通常會定義為領域模型(Domain Model)。
在這種情況下,需要將領域模型與資料庫模型之間進行數據轉換。
領域模型的定義
假設領域模型的 ToDo
類如下:
/// 領域模型:ToDo
class ToDo {
final int? id;
final String title;
final bool isCompleted;
ToDo({
this.id,
required this.title,
this.isCompleted = false,
});
}
領域模型與資料庫模型的轉換處理
使用擴展方法來實現領域模型與資料庫模型之間的轉換處理。
/// 領域模型 → Drift的資料庫模型轉換
extension ToDoToDatabase on ToDo {
TodosCompanion toInsertCompanion() {
return TodosCompanion.insert(
// 插入時id自動設定
title: title,
completed: completed,
);
}
TodosCompanion toUpdateCompanion() {
return TodosCompanion(
id: Value(id),
title: Value(title),
completed: Value(isCompleted),
);
}
}
/// Drift的資料庫模型 → 領域模型轉換
extension DatabaseToDo on Todo {
ToDo toDomain() {
return ToDo(
id: id,
title: title,
isCompleted: completed,
);
}
}
利用映射進行數據操作
以下以使用上述轉換處理的數據操作為例進行介紹。
import 'app_db.dart';
final db = AppDatabase();
// 插入ToDo
Future<void> addToDoItem(ToDo toDo) async {
await db.insertTodo(toDo.toInsertCompanion());
}
// 獲取所有ToDo
Future<List<ToDo>> getToDoItems() async {
final dbTodos = await db.getAllTodos();
return dbTodos.map((dbTodo) => dbTodo.toDomain()).toList();
}
Drift的注意事項
由於Drift是RDB,在更改表格定義或架構時,需要進行版本管理。
此外,新增數據項目時,需在每個版本中撰寫遷移處理。
詳情請參閱官方文檔。
官方文檔)https://drift.simonbinder.eu/Migrations/step_by_step/?h=migration
總結
在實機上的處理速度,Drift與Isar並無太大差異。
由於套件的更新頻率較高,因此也更容易調整與其他套件的依賴關係,使用起來相當方便。
總體而言,我認為Drift是一個不錯的選擇。
大家也請務必嘗試使用Drift進行開發!