【Flutter】ローカルデータベースにデータを保存(永続化)する方法

Flutter

Flutter で、ローカルデータベースにデータを保存する方法を紹介します!
SQLite を扱う sqflite のパッケージを使用していきます。

パッケージのインストール

sqflite のパッケージをインストールします。また、path パッケージも併せてインストールします。

パッケージのインストール方法は、以下の記事をご参照ください。

データベースの操作方法

データベースの操作には、データの取得、追加、更新、削除があります。

データベースの作成

まずは、操作するデータベースを作成します。
以下のように、データベース名やテーブル名、構成を指定します。

Future<Database> initDatabase() async {
  String path = join(await getDatabasesPath(), "my_database.db");
  return await openDatabase(
    path,
    version: 1,
    onCreate: (Database db, int version) async {
      await db.execute('''
        CREATE TABLE my_table(
          id INTEGER PRIMARY KEY,
          title TEXT,
          is_finish INTEGER
        )
      ''');
    },
  );
}

7 行目に、シングルクォーテーションを3つ重ねていますが、これは改行して文字列を生成できるようにするためです。

全データの取得

データベースに保存されている全データを取得するには、以下のように query を使用します。

Future<List<Map<String, dynamic>>> getAllData() async {
  final Database? db = await database;
  return await db!.query('my_table');
}

データの追加

データベースにデータを追加するには、以下のように、追加するデータを指定して insert を使用します。

Future<void> insertData(Map<String, dynamic> data) async {
  final Database? db = await database;
  await db!.insert('my_table', data, conflictAlgorithm: ConflictAlgorithm.replace);
}

データの更新

データの更新を行うには、以下のように、更新するデータの id と更新後のデータを指定して update を使用します。

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

データの削除

データを削除するには、以下のように、削除するデータの id を指定して delete を使用します。

Future<int> deleteData(int id) async {
  final Database? db = await database;
  return await db!.delete('my_table', where: 'id = ?', whereArgs: [id]);
}

使用例

sqflite を使用したアプリの例として、ToDo アプリを作成してみました。
構成は、以下の3つのファイルになっています。

  • database_helper.dart
     データベース操作を管理するファイル
  • todo.dart
     ToDo のクラスファイル
  • todo_view.dart
     ToDo をリストで表示するページを作成するファイル

database_helper の作成

データベースの作成、操作を行う database_hepler は、以下のようになっています。
DatabaseHelper クラスとして、シングルトンで実装しています。

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

class DatabaseHelper {
  static final DatabaseHelper _instance = DatabaseHelper._internal();

  factory DatabaseHelper() {
    return _instance;
  }

  DatabaseHelper._internal();

  static Database? _database;
  Future<Database?> get database async {
    if (_database != null) {
      return _database;
    }
    _database = await initDatabase();
    return _database;
  }

  Future<Database> initDatabase() async {
    String path = join(await getDatabasesPath(), "my_database.db");
    return await openDatabase(
      path,
      version: 1,
      onCreate: (Database db, int version) async {
        await db.execute('''
          CREATE TABLE my_table(
            id INTEGER PRIMARY KEY,
            title TEXT,
            is_finish INTEGER
          )
        ''');
      },
    );
  }

  Future<List<Map<String, dynamic>>> getAllData() async {
    final Database? db = await database;
    return await db!.query('my_table');
  }

  Future<void> insertData(Map<String, dynamic> data) async {
    final Database? db = await database;
    await db!.insert('my_table', data, conflictAlgorithm: ConflictAlgorithm.replace);
  }

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

  Future<int> deleteData(int id) async {
    final Database? db = await database;
    return await db!.delete('my_table', where: 'id = ?', whereArgs: [id]);
  }
}

ToDo クラスの作成

ToDo タスクの構成をクラスとして実装したのが、以下の todo.dart になります。
今回は、ToDo が、id とタイトル、完了済みかどうかの情報を持つようにしました。
また、データベースに保存するために Map に変換するメソッドと、データベースから ToDo 構築するために ToDo クラスへの変換を行うメソッドも用意しています。

class ToDo {
  final int? id;
  final String title;
  final bool isFinish;

  ToDo({
    this.id,
    required this.title,
    required this.isFinish,
  });

  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'title': title,
      'is_finish': isFinish ? 1 : 0,
    };
  }

  factory ToDo.fromMap(Map<String, dynamic> map) {
    return ToDo(
      id: map['id'],
      title: map['title'],
      isFinish: map['is_finish'] == 1,
    );
  }
}

ToDo リストとして表示するページを作成

ToDo リストとして表示するページは、以下のように実装しています。
データベースの操作を行なった際には、_fetchToDoList() でデータベースの更新内容をページに反映させるようにしています。

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

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

  @override
  State<TodoListPage> createState() => _TodoListPageState();
}

class _TodoListPageState extends State<TodoListPage> {
  late List<ToDo> _todoList;
  DatabaseHelper dbHelper = DatabaseHelper();

  @override
  void initState() {
    super.initState();
    _todoList = [];
    _fetchToDoList();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ToDo List'),
      ),
      body: _buildTodoList(),
      floatingActionButton: FloatingActionButton(
        onPressed: _pushAddTodoScreen,
        tooltip: 'Add ToDo',
        child: const Icon(Icons.add),
      ),
    );
  }

  // floatingActionButtonが押された時に表示するToDo追加ページ
  void _pushAddTodoScreen() {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) {
          return Scaffold(
            appBar: AppBar(
              title: const Text('Add a new ToDo'),
            ),
            body: TextField(
              autofocus: true,
              onSubmitted: (val) {
                ToDo newTodo = ToDo(title: val, isFinish: false);
                _insertToDo(newTodo);
                Navigator.pop(context);
              },
              decoration: const InputDecoration(
                  hintText: 'Input a new ToDo title.',
                  contentPadding: EdgeInsets.all(16.0)),
            ),
          );
        },
      ),
    );
  }

  // ToDoListの作成
  Widget _buildTodoList() {
    return ListView.builder(
      itemCount: _todoList.length,
      itemBuilder: (context, index) {
        ToDo todo = _todoList[index];
        return Card(
          child: ListTile(
            title: Text(todo.title),
            leading: Checkbox(
              onChanged: (value) {
                ToDo updatedTodo = ToDo(id: todo.id, title: todo.title, isFinish: value!);
                _updateToDo(updatedTodo);
              },
              value: todo.isFinish,
            ),
            trailing: IconButton(
                icon: const Icon(Icons.delete),
                onPressed: () {
                  _deleteToDo(todo);
                }
            ),
          ),
        );
      },
    );
  }

  // データベースの内容をページに反映
  _fetchToDoList() async {
    List<Map<String, dynamic>> result = await dbHelper.getAllData();
    setState(() {
      _todoList = result.map((map) => ToDo.fromMap(map)).toList();
    });
  }

  _insertToDo(ToDo todo) async {
    await dbHelper.insertData(todo.toMap());
    _fetchToDoList();
  }
  
  _updateToDo(ToDo todo) async {
    await dbHelper.updateData(todo.id!, todo.toMap());
    _fetchToDoList();
  }

  _deleteToDo(ToDo todo) async {
    await dbHelper.deleteData(todo.id!);
    _fetchToDoList();
  }
}

実行例

sqfliteを使用したToDoアプリの例

まとめ

Flutter で、ローカルデータベースを使用して、データを永続化させる方法について、紹介しました!

  • sqflite パッケージで、ローカルデータベースの実装が可能
  • テーブル名や構成を指定してデータベースを生成後、データの取得、追加、更新、削除が可能

以上で、【Flutter】ローカルデータベースにデータを保存(永続化)する方法 は終わりです。

おすすめ書籍

コメント

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