flutter sqflite
対象者
- FlutterでSQLiteを使いたい
- 実際は、sqfliteですが...
- LocalDB使いたい
- SQLの知識が少しある
ORMを使わない生のsqflite
を使っているプロジェクトに最近出会った😅
NoSQLでええやんけ!
そうもいかない😅
なので、検索して調べるがいい感じの資料がない?
メモ書きぐらい作るか📝
今回使用するライブラリ:
プロジェクトの説明
TodoListを作ってみた。しかしハマった点があった💦
Supported SQLite types
No validity check is done on values yet so please avoid non supported types https://www.sqlite.org/datatype3.html
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.
More information on supported types here.
INTEGER
Dart type: int
Supported values: from -2^63 to 2^63 - 1
REAL
Dart type: num
TEXT
Dart type: String
BLOB
Dart type: Uint8List
値の有効性チェックはまだ行われていませんので、サポートされていない型は避けてください https://www.sqlite.org/datatype3.html
DateTime は SQLite でサポートされていない型です。個人的には int (millisSinceEpoch) または string (iso8601) として保存しています。
bool は SQLite でサポートされていない型です。INTEGERと0と1の値を使用してください。
えっbool型ないの?
はい、ないです😇
なので、int
型にして使いましょう!
モデルを作成
idは、?をつけてる。0, 1とかにすると、同じ番号に保存するので、上書きされてします💦
モデル
class Todo {
int? id;
String title;
bool done;
Todo({this.id, required this.title, this.done = false});
// データベースに保存するために、boolをintに変換
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'done': done ? 1 : 0, // boolをintに変換
};
}
// データベースから読み込む際に、intをboolに変換
factory Todo.fromMap(Map<String, dynamic> map) {
return Todo(
id: map['id'],
title: map['title'],
done: map['done'] == 1, // intをboolに変換
);
}
}
DataBase, tableを作成するクラスを作成する。今回は、追加・表示・checkboxだけ、更新・削除を行います。path.dart
というモジュールを読み込む必要がある。パスを操作する必要がある。アプリ内のDBにアクセスするためですかね。
現在は標準機能として、組み込まれているみたい?
api
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:sqflite_todo/model/todo.dart';
// データベースの操作を行うクラス
class DatabaseHelper {
// データベースのバージョン
static const int _databaseVersion = 1;
// データベースの名前
static const String _databaseName = 'todo.db';
// データベースのインスタンスを取得
Future<Database> _getDB() async {
return openDatabase(
join(await getDatabasesPath(), _databaseName), // データベースのパスを指定
version: _databaseVersion, // データベースのバージョンを指定
onCreate: (db, version) async {
// データベースにテーブルを作成
await db.execute(
'CREATE TABLE todos(id INTEGER PRIMARY KEY, title TEXT, done INTEGER)',
);
},
);
}
// Todoをデータベースに追加
Future<int> addTodo(Todo todo) async {
final db = await _getDB(); // データベースのインスタンスを取得
return db.insert(
'todos', // テーブル名
todo.toJson(), // TodoをMap型に変換して挿入
conflictAlgorithm: ConflictAlgorithm.replace, // データが重複した場合は置き換える
);
}
// Todoのdoneを更新
Future<int> updateTodoDone(Todo todo) async {
final db = await _getDB();
return db.update(
'todos',
{'done': todo.done ? 1 : 0}, // doneのみを更新するためのMap
where: 'id = ?',
whereArgs: [todo.id],
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// Todoを削除
Future<int> deleteTodo(int id) async {
final db = await _getDB();
return db.delete(
'todos',
where: 'id = ?',
whereArgs: [id],
);
}
// すべてのTodoを取得
Future<List<Todo>> getTodos() async {
final db = await _getDB();
final List<Map<String, dynamic>> maps = await db.query('todos');
return List.generate(maps.length, (i) {
return Todo(
id: maps[i]['id'],
title: maps[i]['title'],
done: maps[i]['done'] == 1,
);
});
}
}
データを追加するボトムシートを出すボタンと、データの表示、チェックをつける機能、削除機能を実装したコードはこんな感じ。データベースを操作したあとは、画面の更新が必要なので、メソッドを実行するときは、setStateを使用します。
アプリの画面
import 'package:flutter/material.dart';
import 'package:sqflite_todo/api/database_helper.dart';
import 'package:sqflite_todo/model/todo.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isDone = false;
final titleController = TextEditingController();
// DatabaseHelperをインスタンス化
final dbHelper = DatabaseHelper();
void dispose() {
titleController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
List<Todo> todos = [];
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepPurple,
title: const Text('Todo List'),
),
// Todoのリストを表示
body: FutureBuilder<List<Todo>>(
future: dbHelper.getTodos(),
builder: (context, snapshot) {
if (snapshot.hasData) {
todos = snapshot.data!;
return ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(todos[index].title),
leading: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
// Todoを削除
dbHelper.deleteTodo(todos[index].id!);
setState(() {
todos.removeAt(index);
});
},
),
trailing: Checkbox(
value: todos[index].done,
onChanged: (value) {
// Todoのdoneを更新
final todo = Todo(
id: todos[index].id,
title: todos[index].title,
done: value!,
);
dbHelper.updateTodoDone(todo);
setState(() {
todos[index].done = value;
});
},
),
);
},
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// showModalBottomSheetを使用してモーダルボトムシートを表示
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: Container(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min, // コンテンツのサイズに合わせて高さを調整
children: <Widget>[
TextField(
controller: titleController,
decoration: const InputDecoration(
labelText: 'Title',
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
final title = titleController.text;
if (title.isNotEmpty) {
final todo = Todo(
title: title,
);
await dbHelper.addTodo(todo);
setState(() {
// Todoを追加
titleController.clear();
});
Navigator.pop(context);
}
},
child: const Text('Add'),
),
],
),
),
);
},
);
},
tooltip: 'Add',
child: const Icon(Icons.add),
),
);
}
}
感想
bool
型が使えないのが、不便でしたね💦
私は、NoSQLの方が好きなのでそっちを使いますね。Driftってライブラリもあるので、試してみると良いかもしれません。ORMを使うと、処理速度が遅くなる聞いたことありますが...
SQLでなくても良いのであれば、こちらのLocal DBをおすすめします。
よき、Flutterライフを💙💚
Discussion