LoginSignup
47
55

More than 3 years have passed since last update.

Flutterでsqliteを使ったTodoアプリを作る

Posted at

参考にしたサイト

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();
  }
}
47
55
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
47
55