FlutterのDriftパッケージを使うメリットは、「SQLiteデータベースの作成・データの登録・抽出・更新・削除等のデータベースの操作を、SQL文を直接書かなくても、Dartの文法のみで実行できる」こと。
SQLiteは「SQLite3」というパッケージがありますが、SQLite3を使うには、自分でSQL構文を書く必要があります。
SQL構文に不慣れな人が、視覚的な操作なしにSQLデータベースを作るのは大変です。
でも、DriftパッケージにはSQLite3パッケージとの依存関係があって、SQLiteとDartとの橋渡し役をしてくれます。
この記事では、Driftの導入からメソッドの作成・実行までの基本的な流れを紹介します。
- Drift: 2.22.0
- Flutter: 3.24.4
- Dart: 3.5.4
- Android Studio: Koala(2024.1.1)
Driftパッケージのインストール
Driftを使うには、Driftパッケージの他に「drift_flutter」と、開発段階で必要なパッケージを2つインストールする必要があります。
Drift単体では機能しません。
Drift以外の複数のパッケージを一つ一つインストールするのは面倒なので、以下のようにpubspec.yamlに一度に全て入力して、pub getすることにします。
build_runnerは、もしすでに「dev_dependencies:」に入れてあるなら、バージョンをDrift指定のバージョンに書き換えればOKです。
古いバージョンのDriftでは、もっとたくさんのパッケージが必要だったけど、現在のDrift2.22.0では、この4つになっています。
でも、今後変わる可能性はあるので、導入時は公式サイトで確認すべし。
Driftの公式サイト
コマンドラインから必要なパッケージを一度にインストールする場合は、ターミナルで以下のコマンドでいけます。
dart pub add drift drift_flutter dev:drift_dev dev:build_runner
詳細は、公式サイト(https://drift.simonbinder.eu/setup/#the-dependencies)の
’Alternatively, you can achieve the same result using the following command:’の項を参照。
pub.devのDriftのInstallingのページでは、「flutter pub add drift」とコマンドを走らせるように出ているけど、この方法でインストールできるのは、Driftだけです。
コード生成ファイルを定義
Driftを使うための一連の定義は、一つのファイルにまとめておく必要があります。
ここでは、dbフォルダに「database.dart」を作って、ここに定義を書いていきます。
Driftでは、定義ができたら「build_runner」を走らせて、別の定義ファイルを自動作成するのでDrift用のフォルダを作っておくのがオススメ。
定義ファイルには、まず「drift」のimportと「database.g.dart」をpartとして取り込む宣言文を書きます。
「database.g.dart」は、いま自分で作った「database.dart」の拡張子の前に「.g」を入れるのがDriftのルールです。
後で、Dartが「database.g.dart」を自動生成します。
import 'package:drift/drift.dart';
part 'database.g.dart';
この時点ではエラーの赤い線が出てるけど、今はこのままでOK。
テーブルのクラスを作成
次に、DriftのTableをextendsしてデータベースのテーブルを作ります。
import 'package:drift/drift.dart';
part 'database.g.dart';
class TodoItems extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get title => text()();
TextColumn get content => text()();
}
データベース名(ここでは「TodoItems」)は、必ず「複数形」にします。
複数形の単語が「テーブル名」、その単数形が「レコード1行分」というように、Dartが自動で認識します。
テーブルクラスは、ここでは一つだけ作っていますが、複数個作ることができます。
nullableやprimaryKeyの指定、テキストなら最小・最大文字数などもテーブルクラスで指定します。
これらは、このテーブルではやってないけど、integer()には.autoIncrement()()をつけて、暗黙的に主キーとしての設定だけしてみました。
データベースクラスの作成
テーブルの定義ができたら、そのテーブルクラスをもとにしたデータベースのクラスを作成します。
@DriftDatabase(tables: [TodoItems])
class MyDatabase extends _$MyDatabase {
}
@DriftDatabaseのtables: 引数には、作成したテーブルクラスを指定します。
複数形が「Categories」のような、単数形の「Category」に単に「s」をつけない単語をテーブルクラス名に使うときは、次のように「@DataClassName(‘Category’);」をつけて定義します。
@DataClassName('Category');
class Categories extends Table {
hogehoge...
}
「build_runner」で「〜.g.dart」を生成
データベースクラスの大枠が作れたら、ターミナルでbuild_runnerを走らせます。
dart run build_runner build
すると、「database.g.dart」が作成されます。
この「.g.dart」ファイルは、手入力で修正してはいけません。
テーブルクラスの構造を変えたときは、再度build_runnerを走らせます。
もし、すでにbuild_runnerを走らせたことがある場合は、その旨のエラーメッセージが出ることがあります。
その対処法として、ターミナルのメッセージに選択肢が示されるので、Deleteを選びます。
- Delete
- ignore
- ….. たしか、こんな感じの選択肢だったと思う…
データベース接続のコンストラクターを作成
.g.dartのコード生成が終わったら、さきほど作った「MyDatabase」クラスにコンストラクタを作ります。
このコンストラクタで、データベースを保存する場所をDriftに伝えます。
このコードはDriftの公式サイトからコピペして、クラス名(MyDatabaseとextendsする_$の後の名前)と、driftDatabaseの引数(my_database)だけ修正すればOK。
@DriftDatabase(tables: [TodoItems])
class MyDatabase extends _$MyDatabase {
MyDatabase() : super(_openConnection());
static QueryExecutor _openConnection() {
return driftDatabase(name: 'my_database');
}
「driftDatabase」の引数「name: 」は、SQLiteデータベースのファイルとして保存されるファイル名にもなります。
ここでは「my_database」としました。拡張子なしで指定します。
driftDatabase()クラスは「drift_flutterパッケージ」が必要なので、ファイルの冒頭にimport文で追加します。
import 'package:drift_flutter/drift_flutter.dart';
スキーマバージョンの設定
ここまでの段階では、クラス名(MyDatabase)が赤くエラー表示されていると思うけど、このエラーはスキーマバージョンを指定すると解消されます。
スキーマバージョンとは、作成したデータベースのバージョンです。
新規作成時は「1」です。
設定方法は、Drift公式サイトから「schemaVersion」の行をコピペしてもいいけど、クラス名(MyDatabase)から「オプション( )+Enter( )」で「Create 1 missing override」を選ぶとAndroid Studioが入れてくれます。
@override
// TODO: implement schemaVersion
int get schemaVersion => throw UnimplementedError();
上のように自動挿入されたら、「throw UnimplementedError()」の部分を、下のコードのように「1」にすればOK。
@override
int get schemaVersion => 1;
スキーマバージョンの数字は必ず1から始めます。
整数しか使えないので、「1.5」とかはダメです。
データベースの登録・抽出・更新・削除メソッドの作成
前項までが終わると、メソッドが作成できるようになります。
メソッドの作成は、プロジェクトやデータベースの規模に応じて構築方法が違ってくると思うけど、ここでは、最も基礎的な方法でデータベースを操作するSQLクエリをメソッドとして作成していきます。
これらのメソッドも、「database.dart」の中に書きます。
データの登録(Create)INSERT
初めてメソッドを作るときは文法がさっぱりわからないので、intoの引数にはとりあえずテーブル名の先頭を小文字に入力したら、その引数上でF4を押すと、「database.g.dart」の定義に飛ぶので、少し眺めるとなんとなくわかる、気がします…気がします…
Future<void> addTodo(TodoItem item) => into(todoItems).insert(item);
データの抽出(Read)SELECT
Future<List<TodoItem>> get allItems => managers.todoItems.get();
ここでは、get(ゲッター)を使っているけど、getを使わなければ下のように書いても同じ。
Future<List<TodoItem>> getAllItems() async {
return await managers.todoItems.get();
}
データの更新(Update)
下のコードでは、データベースの1行分を丸ごと更新します。
Future<void> updateTodo(TodoItem itemUpdate) =>
managers.todoItems.replace(itemUpdate);
データの削除(Delete)
対象となる1行を削除します。
コード内の「f」は、「filter」の「f」。
カラム名「id」の値でフィルターして、その値がデータベース内の値と一致したら削除する、という感じ。
Future<void> deleteTodo(TodoItem itemDelete) =>
managers.todoItems.filter((f) => f.id(itemDelete.id)).delete();
データベースインスタンスの取得
メソッドができたら、いよいよそれを使う準備です。
データベースのインスタンスは、プロジェクトの中で1つだけ持つよう推奨されているので、トップレベルのグローバル変数として「main.dart」で宣言します。
import 'package:flutter/material.dart';
import 'package:drift_try/db/database.dart';
late MyDatabase database;
void main() {
database = MyDatabase();
runApp(const MyApp());
}
「database.dart」をインポートして、MyDatabaseのクラス変数を宣言します。
先頭にはlateをつけて、null Safetyに対応させます。
参照したのは、Dart公式FAQの「Using the database」の項
クエリメソッドの実行
メソッドの実行は、「ElevatedButton」の「onPressed:」に入れてみました。
ここでは、main.dartだけで画面を作っています。(main.dartの全文はこの記事の最後のちょっと前に載せました)
// データの登録
ElevatedButton(
onPressed: () => _databaseIns(), child: const Text('登録')),
// データの削除
ElevatedButton(
onPressed: () => _databaseDel(), child: const Text('削除')),
// データの更新
ElevatedButton(
onPressed: () => _databaseUpdate(), child: const Text('30を修正')),
データの登録「_databaseIns()」
データベースに3行分を登録するElevatedButtonの処理コード。
_databaseIns() {
TodoItem item;
// 10 を登録
item = const TodoItem(id: 10, title: '1番目', content: '1番目の詳細');
database.addTodo(item);
// 20 を登録
item = const TodoItem(id: 20, title: '2番目', content: '2番目の詳細');
database.addTodo(item);
// 30 を登録
item = const TodoItem(id: 30, title: '3番目', content: '3番目の詳細');
database.addTodo(item);
}
「DB Browser for SQLite」で中身を見てみる
「DB Browser for SQLite」は、SQLiteまたはSQLCipherデータベースファイルを、視覚的に作成・検索・編集できるオープンソースツールです。
「DB Browser for SQLite」公式サイト
「DB Browser for SQLite」でデータベースの中身を見てみるには、メソッドの実行後にsqliteファイルをダウンロードして、「DB Browser for SQLite」で開きます。
Android Studioでsqliteファイルをダウンロードするには、「Device Explorer」から、「_/data/data/(プロジェクト名)/app_flutter/(データベース名).sqlite」を右クリックし、「Save as」です。
※プロジェクト名を確認するには、「(プロジェクトフォルダ)/android/app/build.gradle」というファイルの「android {namespace = “〜〜”」の値。
データの削除「_databaseDel()」
データベースの「id:10」の行を削除するElevatedButtonの処理コード。
_databaseDel() {
TodoItem item;
// 10 を削除
item = const TodoItem(id: 10, title: '1番目', content: '1番目の詳細');
database.deleteTodo(item);
}
「DB Browser for SQLite」で中身を見てみる
データの更新「_databaseUpdate()」
データベースの「id:30」の行を修正するElevatedButtonの処理コード。
_databaseUpdate() {
TodoItem item;
// 30 を修正
item = const TodoItem(id: 30, title: '3番目を修正', content: '3番目の詳細を修正');
database.updateTodo(item);
}
「DB Browser for SQLite」で中身を見てみる
main.dartの全データ
import 'package:flutter/material.dart';
import 'package:drift_try/db/database.dart';
late MyDatabase database;
void main() {
database = MyDatabase();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Driftの導入',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Driftの導入'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// データの登録
ElevatedButton(
onPressed: () => _databaseIns(), child: const Text('登録')),
// データの削除
ElevatedButton(
onPressed: () => _databaseDel(), child: const Text('削除')),
// データの更新
ElevatedButton(
onPressed: () => _databaseUpdate(), child: const Text('30を修正')),
],
),
),
);
}
_databaseIns() {
TodoItem item;
// 10 を登録
item = const TodoItem(id: 10, title: '1番目', content: '1番目の詳細');
database.addTodo(item);
// 20 を登録
item = const TodoItem(id: 20, title: '2番目', content: '2番目の詳細');
database.addTodo(item);
// 30 を登録
item = const TodoItem(id: 30, title: '3番目', content: '3番目の詳細');
database.addTodo(item);
}
_databaseDel() {
TodoItem item;
// 10 を削除
item = const TodoItem(id: 10, title: '1番目', content: '1番目の詳細');
database.deleteTodo(item);
}
_databaseUpdate() {
TodoItem item;
// 30 を修正
item = const TodoItem(id: 30, title: '3番目を修正', content: '3番目の詳細を修正');
database.updateTodo(item);
}
}
Driftの特徴
Driftは、「sqlite3」「sqflite」などのライブラリ上に構築されたパッケージです。
Driftを使うと、SQL文を書かなくてもSQLiteデータベースを扱えますが、SQL文を直接書いても使えます。
DAOもサポートしているし、Providerでラップすることもできます。
この記事では、まずはDriftの導入とその基本概要をまとめました。