TO-DO List를 Database로 CRUD 해보자
pubspec.yaml
dev_dependencies:
flutter_test:
sdk: flutter
sqflite: ^2.0.0+3
path: ^1.8.0
todo.dart : Todo Class
class Todo {
String title;
String content;
int active;
int? id;
Todo({required this.title, required this.content, required this.active, this.id});
Map<String, dynamic> toMap() {
return {
"id": id,
"title": title,
"content": content,
"active": active,
};
}
}
main.dart : 메인화면
import "package:flutter/material.dart";
import "package:sqflite/sqflite.dart";
import "package:path/path.dart";
import 'package:sql_app/todo.dart';
import "addTodo.dart";
import "clearList.dart";
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
Future<Database> db = initDatabase();
return MaterialApp(
title: "SQL Example App",
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: "/",
routes: {
"/": (context) => DatabaseApp(title: "Load Database", db: db),
"/add": (context) => AddTodoApp(title: "Add Todo", db: db),
"/clear": (context) => ClearListApp(title: "Clear Todo", db: db),
},
);
}
Future<Database> initDatabase() async {
return openDatabase(
join(await getDatabasesPath(), "todo_database.db"),
onCreate: (db, version) {
return db.execute(
"CREATE TABLE TODO("
"ID INTEGER PRIMARY KEY AUTOINCREMENT, "
"TITLE TEXT,"
"CONTENT TEXT,"
"ACTIVE INTEGER"
")",
);
},
version: 1,
);
}
}
class DatabaseApp extends StatefulWidget {
final String title;
final Future<Database> db;
const DatabaseApp({super.key, required this.title, required this.db});
@override
State<StatefulWidget> createState() => _DatabaseApp();
}
class _DatabaseApp extends State<DatabaseApp> {
late Future<List<Todo>> todoList;
@override
void initState() {
super.initState();
todoList = _getTodo();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[
TextButton(onPressed: () async {
await Navigator.of(context).pushNamed("/clear");
// When Click Back button
setState(() {
todoList = _getTodo();
});
}, child: const Text("완료한 일", style: TextStyle(color: Colors.white))),
],
),
body: Center(
child: FutureBuilder(
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return const CircularProgressIndicator();
case ConnectionState.waiting:
return const CircularProgressIndicator();
case ConnectionState.active:
return const CircularProgressIndicator();
case ConnectionState.done:
if (snapshot.hasData) {
return ListView.builder(
itemBuilder: (context, index) {
Todo todo = (snapshot.data as List<Todo>)[index];
return ListTile(
title: Text(todo.title, style: const TextStyle(fontSize: 20)),
subtitle: Column(
children: <Widget>[
Text(todo.content),
Text("달성여부 : ${todo.active == 1 ? 'true' : 'false'}"),
Container(
height: 1,
color: Colors.blue,
)
],
),
onTap: () async {
Todo result = await showDialog(
context: context,
builder: (BuildContext context) {
TextEditingController contentController = TextEditingController(text: todo.content);
int radioValue = todo.active;
return AlertDialog(
title: Text("${todo.id} : ${todo.title}"),
content: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
controller: contentController,
keyboardType: TextInputType.text,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Radio(value: 1, groupValue: radioValue, onChanged: (value) {
setState(() {
radioValue = value as int;
});
}),
const Text("성공"),
Radio(value: 0, groupValue: radioValue, onChanged: (value) {
setState(() {
radioValue = value as int;
});
}),
const Text("실패"),
],
),
],
);
},
),
actions: <Widget>[
TextButton(onPressed: () {
setState(() {
todo.content = contentController.value.text;
todo.active = radioValue;
});
Navigator.of(context).pop(todo);
}, child: const Text("Yes")),
TextButton(onPressed: () {
Navigator.of(context).pop(todo);
}, child: const Text("No"))
],
);
});
_updateTodo(result);
},
onLongPress: () async {
Todo result = await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("${todo.id} : ${todo.title}"),
content: Text("'${todo.content}'\nAre you sure you want to delete?"),
actions: <Widget>[
TextButton(onPressed: () {
Navigator.of(context).pop(todo);
}, child: const Text("Yes")),
TextButton(onPressed: () {
Navigator.of(context).pop();
}, child: const Text("No")),
],
);
});
_deleteTodo(result);
},
);
},
itemCount: (snapshot.data as List<Todo>).length,
);
} else {
return const Text("No Data");
}
}
},
future: todoList,
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
onPressed: () async {
final todo = await Navigator.of(context).pushNamed("/add");
if (todo != null) {
_insertTodo(todo as Todo);
}
},
heroTag: null,
child: const Icon(Icons.add),
),
const SizedBox(height: 10),
FloatingActionButton(
onPressed: () async {
_updateAllTodo();
},
heroTag: null,
child: const Icon(Icons.update),
),
],
),
);
}
Future<List<Todo>> _getTodo() async {
final Database db = await widget.db;
final List<Map<String, dynamic>> todoList = await db.query("TODO");
return List.generate(todoList.length, (i) {
return Todo(
title: todoList[i]["TITLE"].toString(),
content: todoList[i]["CONTENT"].toString(),
active: todoList[i]["ACTIVE"] == 1 ? 1 : 0,
id: todoList[i]["ID"]
);
});
}
void _insertTodo(Todo todo) async {
final Database db = await widget.db;
await db.insert("TODO", todo.toMap(), conflictAlgorithm: ConflictAlgorithm.replace);
setState(() {
todoList = _getTodo();
});
}
_updateTodo(Todo todo) async {
final Database db = await widget.db;
await db.update("TODO", todo.toMap(), where: "ID = ?", whereArgs: [todo.id]);
setState(() {
todoList = _getTodo();
});
}
_deleteTodo(Todo todo) async {
final Database db = await widget.db;
await db.delete("TODO", where: "ID = ?", whereArgs: [todo.id]);
setState(() {
todoList = _getTodo();
});
}
_updateAllTodo() async {
final Database db = await widget.db;
await db.rawQuery("UPDATE TODO SET ACTIVE = ? WHERE ACTIVE = ?", [1, 0]);
setState(() {
todoList = _getTodo();
});
}
}
addTodo.dart : Todo 추가
import "package:flutter/material.dart";
import "package:sqflite/sqflite.dart";
import "todo.dart";
class AddTodoApp extends StatefulWidget {
final String title;
final Future<Database> db;
const AddTodoApp({super.key, required this.title, required this.db});
@override
State<StatefulWidget> createState() => _AddTodoApp();
}
class _AddTodoApp extends State<AddTodoApp> {
TextEditingController? titleController;
TextEditingController? contentController;
int _radioValue = 0;
@override
void initState() {
super.initState();
titleController = TextEditingController();
contentController = TextEditingController();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(10),
child: TextField(
controller: titleController,
decoration: const InputDecoration(labelText: "제목"),
),
),
Padding(
padding: const EdgeInsets.all(10),
child: TextField(
controller: contentController,
decoration: const InputDecoration(labelText: "할일"),
),
),
Padding(
padding: const EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Radio(value: 1, groupValue: _radioValue, onChanged: (value) {
setState(() {
_radioValue = value as int;
});
}),
const Text("성공"),
Radio(value: 0, groupValue: _radioValue, onChanged: (value) {
setState(() {
_radioValue = value as int;
});
}),
const Text("실패"),
],
),
),
ElevatedButton(
onPressed: () {
Todo todo = Todo(
title: titleController!.value.text,
content: contentController!.value.text,
active: _radioValue,
);
Navigator.of(context).pop(todo);
},
child: const Text("저장"),
),
],
),
),
);
}
}
clearList.dart : 완료된 Todo 조회
import "package:flutter/material.dart";
import "package:sqflite/sqflite.dart";
import "todo.dart";
class ClearListApp extends StatefulWidget {
final String title;
final Future<Database> db;
const ClearListApp({super.key, required this.title, required this.db});
@override
State<StatefulWidget> createState() => _ClearListApp();
}
class _ClearListApp extends State<ClearListApp> {
late Future<List<Todo>> todoList;
@override
void initState() {
super.initState();
todoList = _getTodo();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: FutureBuilder(
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return const CircularProgressIndicator();
case ConnectionState.waiting:
return const CircularProgressIndicator();
case ConnectionState.active:
return const CircularProgressIndicator();
case ConnectionState.done:
if (snapshot.hasData) {
return ListView.builder(
itemBuilder: (context, index) {
Todo todo = (snapshot.data as List<Todo>)[index];
return ListTile(
title: Text(todo.title, style: const TextStyle(fontSize: 20)),
subtitle: Column(
children: <Widget>[
Text(todo.content),
Text("달성여부 : ${todo.active == 1 ? 'true' : 'false'}"),
Container(
height: 1,
color: Colors.blue,
),
],
),
);
},
itemCount: (snapshot.data as List<Todo>).length,
);
} else {
return const Text("No Data");
}
}
},
future: todoList,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
bool result = await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Are you delete all completed job?"),
actions: <Widget>[
TextButton(onPressed: () {
Navigator.of(context).pop(true);
}, child: const Text("Yes")),
TextButton(onPressed: () {
Navigator.of(context).pop(false);
}, child: const Text("No"))
],
);
},
);
if (result) {
_deleteAllTodo();
}
},
child: const Icon(Icons.remove),
),
floatingActionButtonLocation: FloatingActionButtonLocation.miniCenterFloat,
);
}
Future<List<Todo>> _getTodo() async {
final Database db = await widget.db;
final List<Map<String, dynamic>> todoList = await db.rawQuery("SELECT * FROM TODO WHERE ACTIVE = ?", [1]);
return List.generate(todoList.length, (i) {
return Todo(
title: todoList[i]["TITLE"].toString(),
content: todoList[i]["CONTENT"].toString(),
active: todoList[i]["ACTIVE"] == 1 ? 1 : 0,
id: todoList[i]["ID"],
);
});
}
void _deleteAllTodo() async {
final Database db = await widget.db;
await db.rawQuery("DELETE FROM TODO WHERE ACTIVE = ?", [1]);
setState(() {
todoList = _getTodo();
});
}
}