【Flutter】sqfliteを使ったローカルDBの利用方法【日本語】

sqfliteを使って、FlutterでローカルDBを作成し色々操作してみます。

例も交えて(私もド初心者ですが)初心者向けに説明していきます。気持ち難しかったです。

 

他のプログラミングに関する記事はこちら

※本記事はこちらのサイト様を参考に試してみた結果です(英語のサイトです)

スポンサーリンク


【今回試しに作ったもの】

Bobを挿入→クエリ表示→BobをMaryに更新→クエリ表示→削除→クエリ表示 と操作しています(メッセージが表示されるのみです)

 

【全ソースコード】

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_database/database_helper.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SQFlite Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {

  // database_helper.dartのDataBaseHelperをインスタンス化
  final dbHelper = DatabaseHelper.instance;

  // 画面作成
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('sqflite'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              child: Text('insert', style: TextStyle(fontSize: 20),),
              onPressed: () {_insert();},
            ),
            RaisedButton(
              child: Text('query', style: TextStyle(fontSize: 20),),
              onPressed: () {_query();},
            ),
            RaisedButton(
              child: Text('update', style: TextStyle(fontSize: 20),),
              onPressed: () {_update();},
            ),
            RaisedButton(
              child: Text('delete', style: TextStyle(fontSize: 20),),
              onPressed: () {_delete();},
            ),
          ],
        ),
      ),
    );
  }
  
  // ボタンが押されたときのメソッド類
  
  // insertが押されたときのメソッド
  void _insert() async {
    Map<String, dynamic> row = {
      DatabaseHelper.columnName : 'Bob',
      DatabaseHelper.columnAge  : 23
    };
    final id = await dbHelper.insert(row);
    print('inserted row id: $id');
  }

  // queryが押されたときのメソッド
  void _query() async {
    final allRows = await dbHelper.queryAllRows();
    print('query all rows:');
    allRows.forEach((row) => print(row));
  }

  // updateが押された時のメソッド
  void _update() async {
    Map<String, dynamic> row = {
      DatabaseHelper.columnId   : 1,
      DatabaseHelper.columnName : 'Mary',
      DatabaseHelper.columnAge  : 32
    };
    final rowsAffected = await dbHelper.update(row);
    print('updated $rowsAffected row(s)');
  }

  // deleteが押された時のメソッド
  void _delete() async {
    final id = await dbHelper.queryRowCount();
    final rowsDeleted = await dbHelper.delete(id);
    print('deleted $rowsDeleted row(s): row $id');
  }
}

 

pubspec.yaml

name: flutter_database
description: A new Flutter project.

version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  sqflite: any
  path_provider: any

  cupertino_icons: ^0.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:

 

(lib内に作成する)database_helper.dart

import 'dart:io';

import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';

class DatabaseHelper {
  
  static final _databaseName = "MyDatabase.db"; // DB名
  static final _databaseVersion = 1; // 1で固定?

  static final table = 'my_table'; // テーブル名
  
  static final columnId = '_id'; // 列1
  static final columnName = 'name'; // 列2
  static final columnAge = 'age'; // 列3

  // DatabaseHelperクラスをシングルトンにするためのコンストラクタ
  DatabaseHelper._privateConstructor();
  static final DatabaseHelper instance = DatabaseHelper._privateConstructor();

  // DBにアクセスするためのメソッド
  static Database _database;
  Future<Database> get database async {
    if (_database != null) return _database;
    // 初の場合はDBを作成する
    _database = await _initDatabase();
    return _database;
  }
  
  // データベースを開く。データベースがない場合は作る関数
  _initDatabase() async {
    Directory documentsDirectory = await getApplicationDocumentsDirectory(); // アプリケーション専用のファイルを配置するディレクトリへのパスを返す
    String path = join(documentsDirectory.path, _databaseName); // joinはセパレーターでつなぐ関数
    // pathのDBを開く。なければonCreateの処理がよばれる。onCreateでは_onCreateメソッドを呼び出している
    return await openDatabase(path,
        version: _databaseVersion,
        onCreate: _onCreate);
  }

  // DBを作成するメソッド
  Future _onCreate(Database db, int version) async {
    // ダブルクォートもしくはシングルクォート3つ重ねることで改行で文字列を作成できる。$変数名は、クラス内の変数のこと(文字列の中で使える)
    await db.execute('''
          CREATE TABLE $table (
            $columnId INTEGER PRIMARY KEY,
            $columnName TEXT NOT NULL,
            $columnAge INTEGER NOT NULL
          )
          ''');
  }
  
  // Helper methods

  // 挿入
  Future<int> insert(Map<String, dynamic> row) async {
    Database db = await instance.database; //DBにアクセスする
    return await db.insert(table, row); //テーブルにマップ型のものを挿入。追加時のrowIDを返り値にする
  }

  // 全件取得
  Future<List<Map<String, dynamic>>> queryAllRows() async {
    Database db = await instance.database; //DBにアクセスする
    return await db.query(table); //全件取得
  }

  // データ件数取得
  Future<int> queryRowCount() async {
    Database db = await instance.database; //DBにアクセスする
    return Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM $table'));
  }

  // 更新
  Future<int> update(Map<String, dynamic> row) async {
    Database db = await instance.database; //DBにアクセスする
    int id = row[columnId]; //引数のマップ型のcolumnIDを取得
    print([id]);
    return await db.update(table, row, where: '$columnId = ?', whereArgs: [id]);
  }

  // 削除
  Future<int> delete(int id) async {
    Database db = await instance.database;
    return await db.delete(table, where: '$columnId = ?', whereArgs: [id]);
  }
}

 

 

スポンサーリンク


【説明(pubspec.yaml)】

まずはじめに、Flutterでsqfliteを使ったローカルDBを使用する場合、ライブラリの「sqflite」と「path_provider」を使用する必要があります。

そのため、pubspec.yamlのdependenciesに以下を記載し導入します。

  sqflite: any
  path_provider: any

※ソースコードの13~14行目の記述です

※ライブラリの導入方法の詳細はこちらでも紹介しています

 

【説明(main.dart)】

(長くなるので、必要最低限の説明にします)

 

まずは必要なライブラリのimportについてです。

データベースの操作に関するクラスは後述で説明する「database_helper.dart」で作成するので、それをまずimportします。

import 'package:flutter_database/database_helper.dart';

※ソースコードの2行目の記述です

ちなみに、ただコピーしただけでは恐らくエラーが発生している(朱色の波線が表示されている)と思いますが、その理由はプロジェクト名が異なるためです。

自分の作ったdartファイルをimportしたい場合は「package:[プロジェクト名]/[ファイル名]」と指定しますが、今回はプロジェクト名を「flutter_database」としています。

プロジェクト名を変更するには、pubspec.yamlにあるnameを変えてあげればよいです。

 

 

次にinsert(挿入)するためのメソッドです。

  void _insert() async {
    Map<String, dynamic> row = {
      DatabaseHelper.columnName : 'Bob',
      DatabaseHelper.columnAge  : 23
    };
    final id = await dbHelper.insert(row);
    print('inserted row id: $id');
  }

※ソースコードの60~67行目の記述です

database_helper.dartで定義しているinsertメソッドに、Map<String, dynamic>型のrowを引き渡してます。

Map<String, dynamic>型は、二次元配列というかJsonというか、そんな感じの記述をします。

DatabaseHelperクラスで定義しているcolumnNameの列名に該当する列に「Bob」

DatabaseHelperクラスで定義しているcolumnAgeの列名に該当する列に「23」を設定しています。

 

文字列の中にある「$id」は、変数「id」の値です。

database_helper.dartで定義しているinsertメソッドは、返り値に挿入したidを返す仕組みとなっています。

query~deleteメソッドについては、insertと同様database_helper.dartで定義している各メソッドを呼び出しているのみです。

 

スポンサーリンク


【説明(database_helper.dart)】

(長くなるので、必要最低限の説明にします)

 

まずはじめに、自作のdartファイルを作成するディレクトリについてです。

これが合っているのかどうか自信があまりないですが(笑)lib内に作成するようです。

 

 

次に必要なライブラリをimportします。

既にpubspec.yamlには必要な記述を済ませてありますので、import文で以下を宣言するだけです。

import 'dart:io';

import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';

※ソースコードの1~5行目の記述です

 

 

次に、シングルトンを想定した作りにしていきます。

シングルトンとは、インスタンス化を1つしかしないクラスのことです。

例えば、「age」や「height」などの情報を持っている「human」クラスを作成し、実際の処理で太郎用のhumanクラス、花子用のhumanクラス……というように、たくさんインスタンス化するクラスはシングルトンとは言いません。

今回はインスタンス化を1回しかせずに(main.dartの23行目)使用するため、シングルトンとなります。

恐らく、シングルトンの作りにする場合は、内部の変数とかはfinal(1回設定したら、もう変えられない変数)で宣言する感じになると思います。

それを踏まえて、作成しているのが以下記述です。

  static final _databaseName = "MyDatabase.db"; // DB名
  static final _databaseVersion = 1; // 1で固定?

  static final table = 'my_table'; // テーブル名
  
  static final columnId = '_id'; // 列1
  static final columnName = 'name'; // 列2
  static final columnAge = 'age'; // 列3

  // DatabaseHelperクラスをシングルトンにするためのコンストラクタ
  DatabaseHelper._privateConstructor();
  static final DatabaseHelper instance = DatabaseHelper._privateConstructor();

※ソースコードの9~20行目の記述です

 

databaseVersionは恐らく1で固定です(調べたけども1以外にしているサンプルが見当たらなかった、且つ、1以外を使っているサンプルも見当たらなかった)

 

スポンサーリンク


次に、DBにアクセスするためのメソッドを作成します。

  static Database _database;
  Future<Database> get database async {
    if (_database != null) return _database;
    // 初の場合はDBを作成する
    _database = await _initDatabase();
    return _database;
  }

※ソースコードの23~29行目の記述です

 

getを指定しているメソッドはアクセサーメソッドといって、クラス内の変数の内容を取得したり操作したりするためのメソッドです。

今回は取得するのでgetですが、操作する場合はsetを使います。

もし既にDBが作成されている場合は作成されているDBをreturnで返しますが、もしDBが作成されていない場合は_initDatabaseメソッドでDBを作成し、作成したDBを返します。

 

 

次にDBを作成するメソッドを作成します。

  _initDatabase() async {
    Directory documentsDirectory = await getApplicationDocumentsDirectory(); // アプリケーション専用のファイルを配置するディレクトリへのパスを返す
    String path = join(documentsDirectory.path, _databaseName); // joinはセパレーターでつなぐ関数
    // pathのDBを開く。なければonCreateの処理がよばれる。onCreateでは_onCreateメソッドを呼び出している
    return await openDatabase(path,
        version: _databaseVersion,
        onCreate: _onCreate);
  }

  // DBを作成するメソッド
  Future _onCreate(Database db, int version) async {
    // ダブルクォートもしくはシングルクォート3つ重ねることで改行で文字列を作成できる。$変数名は、クラス内の変数のこと(文字列の中で使える)
    await db.execute('''
          CREATE TABLE $table (
            $columnId INTEGER PRIMARY KEY,
            $columnName TEXT NOT NULL,
            $columnAge INTEGER NOT NULL
          )
          ''');
  }

※ソースコードの32~51行目の記述です

 

getApplicationDocumentsDirectoryメソッドとは、path_providerライブラリに含まれているメソッドで、ファイルを配置するディレクトリのパスを取得できます。

それで取得したディレクトリを、databaseName……今回はMyDatabase.dbでjoinしています。

joinすることで「documentsDirectory.path\MyDatabase.db」みたいな感じに設定してくれます(,で複数区切ることでたくさん繋げることも可能です)

joinはpathライブラリに含まれているメソッドです。

 

openDatabaseメソッドとは、sqfliteライブラリに含まれているメソッドで、もし既にDBが作成されているならば作成されているDBを返し、もし作成されていないならば「onCreate:」で指定されている動作を行います。

今回は_onCreateメソッドを行います。

_onCreateメソッドではCREATE文を作成し、DBを作成しています。

 

 

最後に、挿入(insert)したり更新(update)するためのメソッドを作成します。

Future<int> insert(Map<String, dynamic> row) async {
    Database db = await instance.database; //DBにアクセスする
    return await db.insert(table, row); //テーブルにマップ型のものを挿入。追加時のrowIDを返り値にする
  }

  // 全件取得
  Future<List<Map<String, dynamic>>> queryAllRows() async {
    Database db = await instance.database; //DBにアクセスする
    return await db.query(table); //全件取得
  }

  // データ件数取得
  Future<int> queryRowCount() async {
    Database db = await instance.database; //DBにアクセスする
    return Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM $table'));
  }

  // 更新
  Future<int> update(Map<String, dynamic> row) async {
    Database db = await instance.database; //DBにアクセスする
    int id = row[columnId]; //引数のマップ型のcolumnIDを取得
    print([id]);
    return await db.update(table, row, where: '$columnId = ?', whereArgs: [id]);
  }

  // 削除
  Future<int> delete(int id) async {
    Database db = await instance.database;
    return await db.delete(table, where: '$columnId = ?', whereArgs: [id]);
  }

※ソースコードの56~85行目の記述です

 

insertでは、単純にデータをinsertメソッドで突っ込んでいるだけです。

特に突っ込んだidを返す必要がない場合は以下のような記述になります。

Future insert(Map<String, dynamic> row) async {
    Database db = await instance.database; //DBにアクセスする
    await db.insert(table, row); //テーブルにマップ型のものを挿入。追加時のrowIDを返り値にする
  }

Futureの型指定(<int>)を削り、returnを消しただけです。

 

queryAllRowsでは、全件取得した結果をリストとして返してます。

条件をつけたい場合はdb.query(table)の引数をいじればいいです。

例えば、id1~id10のものだけを表示したい、とかの場合はdb.query(table, where: ‘$columnId < 11’)とかでいいと思います。

queryRowCountでは、SELECT文を発行して現在の総件数を返してます。

 

updateでは、updateメソッドで条件を指定して更新しています。

 

deleteでは、引数のidのデータを削除しています。

 

 

すごく長くなりましたが、FlutterでのローカルDBの操作は以上です。

本記事の内容を元に作成すれば、ある程度のことはできると思います。

 

他のプログラミングに関する記事はこちら

スポンサーリンク