参考にしたサイト
sqliteを使ったListViewの作り方がコードつきで解説されています。
sqliteだけでなくBLoCパターンを使った実装の勉強にもなりました。
Using SQLite in Flutter - Flutter Community - Medium
ソースコード
sqliteなしバージョン
sqliteなしのTodoアプリを作ったときのメモはこちら
https://qiita.com/popy1017/items/6b2c95d3c03b35ae97e0
今回は、上記のコードをベースにsqliteを使う部分を追加していきました。
sqlite使用バージョン(この記事のアウトプット)
必要プラグインをインストール
pubspec.yaml
dependencies:
〜〜〜
sqflite: ^1.2.0
path_provider: ^1.6.0
〜〜〜
sqflite: sqliteをflutterで使うためのプラグイン
path_provider: 保存場所のパスを見つけるためのプラグイン
db_provider.dart を作る
db_provider.dart
class DBProvider {
// privateなコンストラクタ
DBProvider._();
static final DBProvider db = DBProvider._();
}
DBを取得する関数を追加
// db_provider.dart
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:sqflite/sqlite_api.dart';
class DBProvider {
DBProvider._();
static final DBProvider db = DBProvider._();
static Database _database;
Future<Database> get database async {
if (_database != null)
return _database;
// DBがなかったら作る
_database = await initDB();
return _database;
}
Future<Database> initDB() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
// import 'package:path/path.dart'; が必要
// なぜか サジェスチョンが出てこない
String path = join(documentsDirectory.path, "TodoDB.db");
return await openDatabase(path, version: 1, onCreate: _createTable);
}
Future<void> _createTable(Database db, int version) async {
return await db.execute(
"CREATE TABLE Todo ("
"id TEXT PRIMARY KEY,"
"title TEXT,"
"dueDate TEXT,"
"note TEXT"
")"
);
}
}
Futureとasync/await
参考サイト
https://qiita.com/akatsukaha/items/f7c4fa9746e69f9b458d
JavaScriptで言うと、Future → Promise、async/awaitはそのまま。
非同期的な処理を同期的に扱うための書き方。
非同期的な処理としては、ファイル読み込みやDB操作、HTTP通信等がある。
モデルを作る
// todo.dart
class Todo {
String id;
String title;
DateTime dueDate;
String note;
Todo({this.id, @required this.title, @required this.dueDate, @required this.note});
Todo.newTodo() {
title = "";
dueDate = DateTime.now();
note = "";
}
assignUUID() {
id = Uuid().v4();
}
factory Todo.fromMap(Map<String, dynamic> json) => Todo(
id: json["id"],
title: json["title"],
// DateTime型は文字列で保存されているため、DateTime型に変換し直す
dueDate: DateTime.parse(json["dueDate"]).toLocal(),
note: json["note"]
);
Map<String, dynamic> toMap() => {
"id": id,
"title": title,
// sqliteではDate型は直接保存できないため、文字列形式で保存する
"dueDate": dueDate.toUtc().toIso8601String(),
"note": note
};
}
sqliteで使用できる型
https://pub.dev/packages/sqflite
Integer、Real、Text、Blobの4つ。
DateTimeとかBoolはない!!!
DateTimeとかBoolはない!!!(2回目)
DateTime is not a supported SQLite type. Personally I store them as int (millisSinceEpoch) or string (iso8601)
bool is not a supported SQLite type. Use INTEGER and 0 and 1 values.
Bool型がないのに気づくのに1〜2時間かかったので強調(←ちゃんとドキュメント読んでから使えばよかった。。。)
じゃあDateTimeとかBool保存したいときどうすんのって話
sqliteに保存するときは、サポートしている型に変換。
sqliteからデータを取得するときは、Dartの型に変換。
DateTime型と文字列の相互変換はタイムゾーンとか気にしないといけないので少し面倒。
下記がとても参考になりました。
https://qiita.com/TomK/items/132831ab45e2aba822a8
https://medium.com/flutter-jp/datetime-17e618c8e26e
Map型
JavaScriptで言う"key":"value"形式の連想配列的な感じ??
今回使うsqfliteのメソッドがMap<String, dynamic>
を引数にとってる。
factory って何?
詳しくはどんな意味があるのかわかりませんでしたが、なんとなくstaticなコンストラクタのような感じ。
ファクトリーパターンなるものを簡単に実装するためのものらしい。
https://qiita.com/shoheiyokoyama/items/d752834a6a2e208b90ca
Factoryパターン
Factoryクラスがオブジェクトの生成処理に加えて生成するオブ>ジェクトの種類の変更をファクトリの処理の中で動的に行う
CRUD 関数を作る
// db_provider.dart
class DBProvider {
〜〜〜
static final _tableName = "Todo";
createTodo(Todo todo) async {
final db = await database;
var res = await db.insert(_tableName, todo.toMap());
return res;
}
getAllTodos() async {
final db = await database;
var res = await db.query(_tableName);
List<Todo> list =
res.isNotEmpty ? res.map((c) => Todo.fromMap(c)).toList() : [];
return list;
}
updateTodo(Todo todo) async {
final db = await database;
var res = await db.update(
_tableName,
todo.toMap(),
where: "id = ?",
whereArgs: [todo.id]
);
return res;
}
deleteTodo(String id) async {
final db = await database;
var res = db.delete(
_tableName,
where: "id = ?",
whereArgs: [id]
);
return res;
}
}
BLoCクラスをDBProviderを使うように修正する
// todo_bloc.dart
class TodoBloc {
final _todoController = StreamController<List<Todo>>();
Stream<List<Todo>> get todoStream => _todoController.stream;
getTodos() async {
_todoController.sink.add(await DBProvider.db.getAllTodos());
}
TodoBloc() {
getTodos();
}
dispose() {
_todoController.close();
}
create(Todo todo) {
todo.assignUUID();
DBProvider.db.createTodo(todo);
getTodos();
}
update(Todo todo) {
DBProvider.db.updateTodo(todo);
getTodos();
}
delete(String id) {
DBProvider.db.deleteTodo(id);
getTodos();
}
}