[基礎知識]Moor(drift)によるDB操作

driftパッケージは、SQLiteの上に構築されており、型安全で、より高レベルのAPIを提供します。

Moor(drift)パッケージの特徴:

メリット:

(1) 型安全性:

  • Dart言語の型システムと統合されており、型安全なクエリが書けます。これにより、コンパイル時に型チェックが行われ、ランタイムエラーを減らすことができます。

(2) 高レベルの抽象化:

  • SQLクエリをDartコードとして書けるため、クエリの作成が直感的で簡単です。特に複雑なクエリやリレーションを管理する場合に有用です。

(3) 自動コード生成:

  • build_runner を使用して、自動的にデータベース関連のコードを生成します。これにより、手動でコードを書く手間を減らし、ミスを減らすことができます。

(4) リレーションの管理:

  • リレーショナルデータベースの特性を活かし、複雑なリレーションやクエリを簡単に管理できます。

デメリット:

(1) パフォーマンスのオーバーヘッド:

  • 高レベルの抽象化に伴うオーバーヘッドがあり、パフォーマンスが若干低下することがあります。

(2) 依存関係の増加:

  • moor (drift) を使用するためには、build_runner や他の依存関係が必要となり、プロジェクトの複雑さが増します。

(3) 学習曲線:

  • 高度な機能を持っているため、学習曲線がやや急です。特に、FlutterやDartに不慣れな初心者には難しい場合があります。

上記の様に、(前回記事に掲載した)sqfliteパッケージがSQLiteを操作する場合に、型を意識しない(つまり、いわゆる文字列として扱う)のに対して、Moor(drift)では(Dart言語の型システムを利用して)方安全なクエリを書くことができる、というのが、大きな違いになっています。

どちらのパッケージを使用するかは、プロジェクトの要件や開発者のスキルセットに依存します。
シンプルで直接的なデータベース操作が必要な場合はsqflite、型安全性と高レベルの抽象化が重要な場合はMoor (drift)を選択するのが良いでしょう。

以下、Moor(drift)の利用手順です。

Moor(drift)の利用手順:

1.Flutterプロジェクト作成

(1) ターミナルを開き、以下のコマンドを実行して新しいプロジェクトを作成します。

flutter create drift_example

(2) プロジェクトフォルダに移動します。

cd drift_example

2.必要なパッケージの追加

Driftを使用するために必要なパッケージ(次の4つ)を追加します。

drift と path_provider と sqlite3_flutter_libs と path は、アプリ実行時に使用されます。
故に、pubspec.yaml では、dependencies 句に登録します。

しかし、drift_dev と build_runner は、開発段階で使用します。
故に、pubspec.yaml では、dev_dependencies 句に登録します。

https://pub.dev/packages/drift/install
https://pub.dev/packages/path_provider/install
https://pub.dev/packages/sqlite3_flutter_libs/install
https://pub.dev/packages/path/install
https://pub.dev/packages/drift_dev/install
https://pub.dev/packages/build_runner/install

2.1 アプリ実行時に必要な(drift、path_provider、sqlite3_flutter_libs、path)パッケージの追加

(1) プロジェクトフォルダで次のコマンドによりdrift、path_provider、sqlite3_flutter_libs、path の各パッケージ(の依存関係)を追加します。

flutter pub add drift
flutter pub add path_provider
flutter pub add sqlite3_flutter_libs
flutter pub add puth

(2) 次のコマンドで上記追加を反映します。

flutter pub get

(3) pubspec.yaml ファイルを開き、上記パッケージ(の依存関係)が追加されていることを確認します。

dependencies:
  flutter:
    sdk: flutter

  drift: ^2.18.0
  path_provider: ^2.1.3
  sqlite3_flutter_libs: ^0.5.23
 path: ^1.9.0

2.2 開発時に必要な(drift_dev、build_runner)パッケージの追加

(1) pubspec.yaml ファイルを開き、上記パッケージ(の依存関係)を追加します。

dev_dependencies:
  flutter_test:
    sdk: flutter

  drift_dev: ^2.18.0
  build_runner: ^2.4.11

(2) ターミナルで以下のコマンドを実行してパッケージをインストール(反映)します。

flutter pub get

3.Driftデータベースの設定と使用

以下は、Driftデータベースを設定し、使用するためのサンプルコードです。

3.1 テーブルとデータベースの設定

まず、テーブルとデータベースを定義します。

(1) lib フォルダ内に database.dart ファイルを作成します。

(2) database.dart の内容:

import 'dart:io';

import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';

part 'database.g.dart'; // 自動生成されるファイルを指定

// テーブル定義
class Items extends Table {
  IntColumn get id => integer().autoIncrement()(); // 自動増分するプライマリキー
  TextColumn get name => text()(); // アイテムの名前
}

// データベースクラスの定義
@DriftDatabase(tables: [Items])
class AppDatabase extends _$AppDatabase {
  AppDatabase() : super(_openConnection());

  @override
  int get schemaVersion => 1; // スキーマバージョンを設定

  // アイテムを追加
  Future<int> insertItem(ItemsCompanion item) => into(items).insert(item);

  // アイテムを取得
  Future<List<Item>> getAllItems() => select(items).get();

  // アイテムを削除
  Future<int> deleteItem(int id) => (delete(items)..where((tbl) => tbl.id.equals(id))).go();
}

// データベース接続を作成
LazyDatabase _openConnection() {
  return LazyDatabase(() async {
    final dbFolder = await getApplicationDocumentsDirectory();
    final file = File(p.join(dbFolder.path, 'db.sqlite'));
    return NativeDatabase(file);
  });
}

3.2 TypeAdapter生成

次に、Driftのコード生成機能を使用して必要なコードを生成します。

(1) ターミナルで以下のコマンドを実行してTypeAdapterを生成します。

flutter pub run build_runner build

3.3 サンプルアプリの作成

次に、サンプルアプリを作成してDriftデータベースを利用します。

(1) lib/main.dart ファイルを開き、以下のコードを追加します。

import 'package:drift/drift.dart';
import 'package:flutter/material.dart';

import 'database.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized(); // Flutterバインディングの初期化
  runApp(const MyApp());
}

// メインアプリケーションクラス
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Drift Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

// ホーム画面クラス
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  MyHomePageState createState() => MyHomePageState();
}

// ホーム画面の状態を管理するクラス
class MyHomePageState extends State<MyHomePage> {
  final AppDatabase _database = AppDatabase(); // データベースのインスタンス
  final TextEditingController _controller = TextEditingController(); // テキストフィールドのコントローラ
  List<Item> _items = []; // 取得したアイテムを格納するリスト

  @override
  void initState() {
    super.initState();
    _loadItems(); // アイテムをロード
  }

  // データベースからアイテムを読み込むメソッド
  Future<void> _loadItems() async {
    final items = await _database.getAllItems();
    setState(() {
      _items = items;
    });
  }

  // アイテムを追加するメソッド
  Future<void> _addItem(String name) async {
    final newItem = ItemsCompanion(
      name: Value(name),
    ); // 新しいアイテムを作成
    await _database.insertItem(newItem); // アイテムをデータベースに追加
    _controller.clear(); // テキストフィールドをクリア
    _loadItems(); // アイテムを再読み込み
  }

  // アイテムを削除するメソッド
  Future<void> _deleteItem(int id) async {
    await _database.deleteItem(id); // 指定したIDのアイテムを削除
    _loadItems(); // アイテムを再読み込み
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Drift Example'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(8.0),
        children: [
          TextField(
            controller: _controller,
            decoration: InputDecoration(
              labelText: 'Item Name',
              suffixIcon: IconButton(
                icon: const Icon(Icons.add),
                onPressed: () {
                  if (_controller.text.isNotEmpty) {
                    _addItem(_controller.text); // アイテムを追加
                  }
                },
              ),
            ),
          ),
          const SizedBox(height: 10),
          ..._items.map((item) => ListTile(
            title: Text(item.name), // アイテムの名前を表示
            trailing: IconButton(
              icon: const Icon(Icons.delete),
              onPressed: () {
                _deleteItem(item.id); // アイテムを削除
              },
            ),
          )),
        ],
      ),
    );
  }
}

3.4 動作確認

下の様に、Item Name(TextField)に入力し、+アイコン(IconButton)をクリックすると、その都度、入力データがSQLiteに登録され、その下の表示欄(ListTile)に表示されていきます。

4.まとめ

drift の利用手順は、
①パッケージ追加(アプリ実行時に必要な4つ(drift、path_provider、sqlite3_flutter_libs、path))
②パッケージ追加(開発時に必要な2つ(drift_dev、build_runner))
③Hiveで使用するデータのモデルクラス作成(例:database.dart)
④コード生成機能によるTypeAdapter生成(例:database.g.dart)
⑤サンプルコード作成(例:main.dart)
という順番で行います。

上記②~④を注意すれば、極めて容易に、DB操作を実現できます。

コメントを残す