【Flutter入門】DB(SQLite)のインストールと基本的な使い方

Flutter

FlutterでDBを扱う方法はいくつかありますが、代表的な方法の一つがSQLiteです。ネットワーク通信が必要なアプリでFirebaseなどのバックエンドサービスを導入するのであればそちらのDBを使うのが良いと思いますが、ローカルで完結するアプリであればSQLiteの利用がおすすめです。

そこで、今回はFlutterでローカルにSQLiteのDBを構築し、基本的な操作を行う方法について説明します。

SQLiteのインストール

FlutterでSQLiteを扱うためのパッケージでsqfliteがあります。以下のようにpubspec.yamlファイル依存関係を追加します。アプリの初期起動を判定するのにshared_preferencesライブラリを使うので同時にインストールを行います。

dependencies:
  sqflite: ^2.2.5
  shared_preferences: ^2.0.18

flutter pub getを実行して、依存関係を解決します。

データベースの作成

sqfliteパッケージを使って、データベースを作成します。データベースの作成は、main関数でアプリケーションの初回起動時の一度だけ実行する方法があります。SharedPreferencesライブラリを使って、初回起動フラグを管理することで、初回起動時のみデータベースの作成処理を実行することができます。

以下、データベース作成のサンプルコードです。

import 'package:flutter/material.dart';
import 'package:path/path.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sqflite/sqflite.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // SharedPreferencesオブジェクトを取得
  final SharedPreferences prefs = await SharedPreferences.getInstance();
  // 初回起動かどうかを判定
  bool isDbCreated = prefs.getBool('isDbCreated') ?? false;
  if (!isDbCreated) {
    // DBを作成
    final Future<Database> database = openDatabase(
      join(await getDatabasesPath(), 'my_database.db'),
      onCreate: (db, version) {
        return db.execute(
          'CREATE TABLE my_table(id INTEGER PRIMARY KEY, name TEXT)',
        );
      },
      version: 1,
    );
    // DBが完全に作成されるまで待機
    await database;
    // 初回起動フラグを更新
    prefs.setBool('isDbCreated', true);
  }

  runApp(const MainApp());
}

DB作成について詳しく解説します。openDatabase関数を使用して、データベースを開いています。この関数はデータベースがまだ作成されていない場合、DBを構築しonCreateコールバック関数を呼び出すので、そこでtableの作成を行なっています。

データベースのパスは join(await getDatabasesPath(), ‘my_database.db’) の部分です。getDatabasesPath()関数はアプリのデータベースファイルが保存されるディレクトリのパスを取得します。

最後に、await databaseを使用して、openDatabase関数が完了するのを待ちます。

FlutterでCRUD作成

以下はデータベースの基本操作を実装したサンプルコードです。参考程度に、DBから取得したデータをリストで表示する部分も実装しています。

import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class MyTablePage extends StatefulWidget {
  const MyTablePage({Key? key}) : super(key: key);

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

class MyTablePageState extends State<MyTablePage> {
  late final Future<List<Map<String, dynamic>>> _items;

  @override
  void initState() {
    super.initState();
    _items = read('test');
  }

  Future<Database> getDB() async{
    return openDatabase(
      join(await getDatabasesPath(), 'my_database.db'),
    );
  }

  Future<int> create(String name) async{
    // Create
    final Database db = await getDB();
    int id = await db.insert(
      'my_table',
      {'name': name}
    );
    return id;
  }

  Future<List<Map<String, dynamic>>> read(String name) async{
    final Database db = await getDB();
    // DBからデータを取得
    final List<Map<String, dynamic>> maps = await db.query(
      'my_table',
      where: 'name = ?',
      whereArgs: [name],
      orderBy: 'id DESC', // ASC: 昇順, DESC: 降順
    );
    return maps;
  }

  Future<int> update(int id, String name) async {
    // Update
    final Database db = await getDB();
    final Map<String, dynamic> data = {
      'id': id,
      'name': name
    };
    return await db.update('my_table', data, where: 'id = ?', whereArgs: [id]);
  }

  Future<int> delete(int id) async {
    // Delete
    final Database db = await getDB();
    return await db.delete('my_table', where: 'id = ?', whereArgs: [id]);
  }
  
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<List<Map<String, dynamic>>>(
      future: _items,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          final items = snapshot.data!;
          return ListView.builder(
            itemCount: items.length,
            itemBuilder: (context, index) {
              final item = items[index];
              return ListTile(
                title: Text(item['id'].toString()),
                subtitle: Text(item['name'] as String),
                onTap: () {
                  print('${item['name']} was tapped!');
                },
              );
            },
          );
        } else if (snapshot.hasError) {
          return Text('${snapshot.error}');
        } else {
          return const Center(
            child: CircularProgressIndicator(),
          );
        }
      },
    );
  }
}

Create: レコードの作成には、insertメソッドを使用します。最初の引数はテーブル名、2番目の引数は挿入するデータのMapです。挿入が成功した場合、メソッドは挿入したレコードのIDを返します。

Read: queryメソッドでデータベースからデータを取得できます。第一引数としてテーブル名をとり、条件はwhereパラメータで指定し、whereに渡すパラメータはwhereArgsに記述します。

update: updateメソッドでレコードを更新します。最初の引数はテーブル名、2番目の引数は挿入するデータのMapです。 queryメソッドと同様、条件はwhereパラメータで指定し、whereに渡すパラメータはwhereArgsに記述します。

delete: deleteメソッドでレコードを削除します。最初の引数はテーブル名です。queryメソッドと同様、条件はwhereパラメータで指定し、whereに渡すパラメータはwhereArgsに記述します。

今回、insertやupdateの際にmapを関数内に作成して使いました。慣れてきたら、my_tableテーブルのレコードを扱うためのクラスを定義することで、コードの再利用性やメンテナンス性が向上するのでチェレンジしてみてください。

タイトルとURLをコピーしました