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