#本記事の目的
皆さんこんにちは!現役大学生のKamichan_R(@Kamichan_R)です。
今回は、Flutterのアプリ制作で利用したSQLiteの動作の学習の振り返りとして本記事を作成しました。
CRUD機能はデータベースの基礎なので、しっかりと定着させる必要があります。ぜひ参考にしていただけたらと思います。
この記事では私がFlutterでToDoアプリを制作した際のコードの解説みたいなことをします。私が書いたコードは以下のリンクから閲覧できます。(実際の解説では、わかりやすくするためにコードを少し変更しています。)
#データベースの実装
それでは、早速データベースを作ります。手順としては、初めにデータベースのファイルを作成します。次にどのようなデータを加えるかを決定します。そして、レコードをCRUD機能を順番に実装していきます。
##データベース作成
データベースのファイルを作成します。
Future<Database> get database async {
return _database ??= await _initDatabase(); // (1)
}
Future<Database> _initDatabase() async {
final path = join(await getDatabasesPath(), 'tasks.db'); //(2)
return openDatabase(
path,
version: 1,
onCreate: _onCreate, //(3)
);
}
(1)では既にデータベースが作られているときはそのデータベースが返されて、まだデータベースが無い場合は_initDatabase()が呼ばれて、新しいデータベースが作成されます。
(2)ではデータベースのファイルのパスをpathに代入しています。ファイルの名前はtasks.dbにしました。
(3)の_onCreateではどのようなデータベースを作成するかを指定します。_onCreateをどうするのかは次の「レコードの構成を考える」に書きます。
##レコードの構成を考える
初めに、どのようなデータを蓄えるかを決定します。今回私が作ったToDoアプリでは、
- ID (_id) (int型)
- タスク名 (name) (String型)
- タスク優先度 (priority) (int型)
- タスクの期日 (deadline) (String型)
- メモ (memo) (String型, Nullも可)
の5つに決めました。
IDは各タスクを一意に決める主キーの役割があります。データベースの作成の際、データを一意に識別できる仕組みが無いと読取、更新、削除の際に対象のデータが選択できないなどのトラブルが起こります。さらに、主キーは永続的でデータが更新されても変わらない必要があります。これは、主キーが削除されたり更新されたりすると、識別不可能になってしまうからです。
Deadlineは、MySQLなどではDateTime型が使えますが、SQLiteではそれが使えないのでString型にしています。
これらを踏まえて、データベースの作成をするSQL文は以下のようになります。(テーブル名はtasksにしました。)
Future _onCreate(Database database, int version) async {
await database.execute('''
CREATE TABLE tasks (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
name STRING NOT NULL,
priority INTEGER NOT NULL,
deadline STRING NOT NULL,
memo STRING
)
''');
}
SQL文中の"INTEGER PRIMARY KEY AUTOINCREMENT"はレコードが作成されると同時に、順番に1, 2, 3,...と自動的に入力されます。これを主キーにすると後々便利なので今回はこれを活用します。
##レコードの作成(Create)
実際のレコードを作成していきます。データベースにレコードを追加するときはinsertを使います。例えば、
- タスク名 (name):task_example
- タスク優先度 (priority):3
- タスクの期日 (deadline) : 2021/04/01 12:34:56.000
- メモ (memo): memo_example
というレコードを追加するとします。このとき
database.insert('tasks', {
name: 'task_example',
priority: 3,
deadline: DateFormat('yyyy/MM/dd HH:mm:ss').format(DateTime(2021, 4, 1, 12, 34, 56)),
memo: 'memo_example'
});
とすればレコードが追加できます。IDは自動的に割り振られるので記入する必要はありません。
DateFormatの必要性
データの入力のとき、日付と時刻はDartではDateTime型を使うことができます。しかし、SQLiteではDateTime型は対応していないのでString型に変換する必要があります。そこで用いるものがDateFormatです。
##レコードの読取(Read)
データの読取は特定のデータまたは格納されているすべてのデータを読み取る処理の2つを実装します。
最初に特定のデータを読み取る処理を実装します。取得したいデータのIDを使ってデータを呼び出します。
database.query(
'tasks',
columns: ['_id', 'name', 'priority', 'deadline', 'memo'],
where: '_id = ?',
whereArgs: [id], // idには取得したいデータのidが入ります
);
whereの'?'とwhereArgにはSQLインジェクションを防ぐ役割があります。そして、これはJSON形式で返されます。
次に、すべてのデータを読み取る処理を実装します。特定のデータを読み取る処理からIDの条件を除いたものになります。しかし、ただすべてのデータを読み取るだけではなく、使いやすいように取り出す順番も変えてみましょう。ここでは、期日が早い順番に取り出します。そのためには以下のコードを入力します。
database.query(
'tasks',
orderBy: 'deadline ASC', // これによって期日が早い順番に取り出される(DESCの場合は遅い順)
);
##レコードの更新(Update)
データを更新するためには、どのデータを更新するかを特定し、更新後のデータが何になるかが入力されれば出来ます。つまり、IDを指定してお目当てのレコードを取ってきて、更新後のデータを入力します。そして、そのためのコードはinsertをupdateにして、whereでIDを指定すれば大丈夫です。
database.update(
'tasks',
{
'name': 'task_updated',
'priority': 5,
'deadline': DateFormat('yyyy/MM/dd HH:mm:ss').format(DateTime(2021, 1, 1, 00, 00, 00)),
'memo': 'memo_updated',
},
where: '_id = ?',
whereArgs: [id] // idには更新したいデータのidが入ります
);
##レコードの削除(Delete)
データの削除のためには、削除したいレコードのIDがわかれば簡単に削除することが出来ます。データの削除にはdeleteを使います。
database.delete(
'tasks',
where: '_id = ?',
whereArgs: [id], // idには削除したいデータのidが入ります
);
#まとめ
データベースの作成自体はシンプルなもので、データベースのファイルやテーブルを作成して、そこに格納されるデータの構成を決めて、データ型に注意してデータを追加していく、というのが基本です。CRUD機能もコード自体はシンプルなもので一度書いてしまえば簡単に繰り返し使えます。皆さんも是非一度データベースを作ってみてください。
#最後に
私が作ったToDoアプリに関する記事はこちらにもあります。ぜひご覧ください。また、私自身のポートフォリオサイトもあります。こちらもよろしければご覧ください。