【本地RDB】高速!介紹Drift的概要與使用方法!!

在應用程式的設計中,特定的數據需要永久保存。
此時,我們會設計在用戶手機內建立的本地資料庫(Local DB)。

前幾天介紹了Isar,但個人評價是,Isar的更新頻率較低,令人不安。
因此,這次將介紹不輸給Isar的高速RDB——Drift。

究竟在什麼情況下需要本地資料庫?

通常,應用程式中使用的變數或狀態(State)在應用程式結束時,
數據也會一併消失,但有些應用程式如備忘錄或聊天記錄等,
希望在下次啟動時也能使用這些數據。

為了保存這類數據,我們使用本地資料庫。

什麼是Drift?

Drift是一個強大的本地資料庫庫,適用於Flutter和Dart。
它基於SQLite,提供類型安全的查詢構建器、反應式的數據流,
以及直觀的數據操作。舊名為Moor。

Drift的主要特點

通過將數據保存到本地資料庫中,
下次啟動時也可以使用這些數據。

Drift具有以下特點:

  1. 類型安全的查詢
    Drift利用Dart的類型系統,在編譯時檢測查詢錯誤。
    在編碼過程中即可檢測到錯誤,這點非常方便!
  2. 反應式數據流
    能夠實時檢測資料庫的變更,並自動更新UI。
  3. 可擴展性與自定義性
    Drift具有高度的可擴展性,可以輕鬆添加自定義函數或觸發器。
    複雜的查詢或事務也能輕鬆處理,靈活性高!
  4. 豐富的文檔
    Drift由詳細的文檔和活躍的社群支持,
    查看文檔即可大部分功能都能掌握!?
    官方文檔)https://drift.simonbinder.eu/setup/
  5. 活躍的更新頻率
    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進行開發!

标题和URL已复制