본문 바로가기

그 땐 App했지/그 땐 Flutter했지

[TAVE/Study]Do it! Flutter 10장 데이터베이스에 데이터 저장하기②

728x90

211011 월

내 주말 어디갔징?

 

📌 이제 CRUD를 알아보자!

데이터 입력하기

📌 플로팅 버튼을 누르면 데이터 입력 화면으로 이동하고 저장하기를 누르면 데이터 베이스에 저장되도록 하자!

class _AddTodoApp extends State<AddTodoApp> {
  TextEditingController? titleController;
  TextEditingController? contentController;
  
  @override
  void initState() {
    super.initState();
    titleController = new TextEditingController();
    contentController = new TextEditingController();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Todo 추가'),
      ),
      body: Container(
        child: Center(
          child: Column(
            children: <Widget>[
              Padding(
                padding: EdgeInsets.all(10),
                child: TextField(
                  controller: titleController,
                  decoration: InputDecoration(labelText: '제목'),
                ),
              ),
              Padding(
                padding: EdgeInsets.all(10),
                child: TextField(
                  controller: contentController,
                  decoration: InputDecoration(labelText: '할일'),
                ),
              ),
              ElevatedButton(
                onPressed: () {
                  Todo todo = Todo(
                    title: titleController!.value.text,
                    content: contentController!.value.text,
                    active: 0);
                  Navigator.of(context).pop(todo);
                },
                child: Text('저장하기'),
              )
            ],
          ),
        ),
      ),
    );

👉🏻 TextEditingController로 데이터를 입력하는 칸을 만든다. 

✍🏻 pop 함수로 데이터를 메인에 전달한다.

  void _insertTodo(Todo todo) async {
    final Database database = await widget.db;
    await database.insert('todos', todo.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace);
  }

👉🏻 widget.db를 이용해 database 객체를 선언하고 insert함수로매개변수로 전달받은 데이터를 입력한다. todos라는 테이블에 데이터를 Map 형태로 바꿔준다.

✍🏻 widget.db를 이용하면 현재 State 상위에 있는 StatefulWidget에 있는 db 변수를 사용할 수 있다. 

✍🏻 conflictAlgorithm은 충돌이 발생할 때를 대비한 것이다. id값이 충돌하면 replace로 새로운 데이터로 교체한다.

      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          final todo = await Navigator.of(context).pushNamed('/add');
          if (todo != null) {
            _insertTodo(todo as Todo);
          }
        },

👉🏻 플로팅 버튼에 _insert함수를 추가한다.

(영상)

 

데이터 검색하기
class _DatabaseApp extends State<DatabaseApp> {
  Future<List<Todo>> getTodos() async {
    final Database database = await widget.db;
    final List<Map<String, dynamic>> maps = await database.query('todos');
    
    return List.generate(maps.length, (i) {
      int active = maps[i]['active'] == 1 ? 1 : 0;
      return Todo(
        title: maps[i]['title'].toString(),
        content: maps[i]['content'].toStirng(),
        active: active,
        id: maps[i]['id']);
    });
  }

👉🏻 데이터를 가져오는 getTodos 함수를 만든다. maps 목록을 활동해 List.generate함수에서 할 일 목록에 표시할 각 아이템을 만든다.

class _DatabaseApp extends State<DatabaseApp> {
  Future<List<Todo>>? todoList;
  
  @override
  void initState() {
    super.initState();
    todoList = getTodos();
  }

👉🏻 할 일 목록은 따로 변수를 선언해 initState함수에서 호출한다.

      body: Container(
        child: Center(
          child: FutureBuilder(
            builder: (context, snapshot) {
              switch(snapshot.connectionState) {
                case ConnectionState.none:
                  return CircularProgressIndicator();
                case ConnectionState.waiting:
                  return CircularProgressIndicator();
                case ConnectionState.active:
                  return CircularProgressIndicator();
                case ConnectionState.done:
                  if(snapshot.hasData) {
                    return ListView.builder(
                      itemBuilder: (context, index) {
                        Todo todo = (snapshot.data as List<Todo>)[index];
                        return Card(
                          child: Column(
                            children: <Widget>[
                              Text(todo.title!),
                              Text(todo.content!),
                              Text('${todo.active == 1 ? 'true' : 'false'}'),
                            ],),
                        );
                      },
                      itemCount: (snapshot.data as List<Todo>).length,
                    );
                  } else {
                    return Text('No data');
                  }
              }
              return CircularProgressIndicator();
            },
            future: todoList,
          ),
        ),
      ),

👉🏻 서버에서 데이터를 받거나 파일에 데이터를 가져올 때 사용한다. switch문으로 데이터를 얼마나 가져왔는지 상태를 표시한다. 

 

데이터 수정하기
                       return ListTile(
                          title: Text(
                            todo.title!,
                            style: TextStyle(fontSize: 20),
                          ),
                          subtitle: Container(
                            child: Column(
                              children: <Widget>[
                                Text(todo.content!),
                                Text('체크 : ${todo.active == 1 ? 'true' : 'false'}'),
                                Container(
                                  height: 1,
                                  color: Colors.blue,
                                )
                              ],
                            ),
                          ),
                        );

👉🏻 할 일 목록을 리스트 뷰로 꾸민다.

✍🏻 ListTile 위젯: title, subtitle, leading, trailing 옶션으로 위젯의 위치를 지정할 수 있다. / Card 위젯: 카드는 칸막이를 만들고 그 안에서 위젯을 자유롭게 꾸민다.

                          onTap: () async {
                            Todo result = await showDialog(
                              context: context,
                              builder: (BuildContext context) {
                                return AlertDialog(
                                  title: Text('${todo.id} : ${todo.title}'),
                                  content: Text('Todo를 체크하시겠습니까?'),
                                  actions: <Widget>[
                                    TextButton(
                                      onPressed: () {
                                        setState(() {
                                          todo.active == 1
                                              ? todo.active = 0
                                              : todo.active = 1;
                                        });
                                        Navigator.of(context).pop(todo);
                                      },
                                      child: Text('예')),
                                  TextButton(
                                    onPressed: () {
                                      Navigator.of(context).pop(todo);
                                    },
                                    child: Text('아니요')),
                                  ],
                                );
                              });
                            _updateTodo(result);
                          },

👉🏻 showDialog함수로 알림 창을 호출한다. 실제 알림창은 AlertDialog로 만든다.

✍🏻 FlatButton으로 예, 아니오 버튼을 만들고 pop함수로 데이터를 전달한다. 

  void _updateTodo(Todo todo) async {
    final Database database = await widget.db;
    await database.update(
      'todos',
      todo.toMap(),
      where: 'id = ?',
      whereArgs: [todo.id],
    );
    setState(() {
      todoList = getTodos();
    });
  }

👉🏻 database.update함수로 데이터를 수정한다.

✍🏻 where와 whereArgs에 전달할 인자를 살펴보자. where는 어떤 데이터를 수정할 것인지 나타내고 id값으로 수정할 데이터를 찾는다. 매개변수 todo로 받은 데이터의 id값을 whereArgs에 설정하고 이 값으로 데이터 베이스에서 수정할 데이터를 찾는다.

 

                          onTap: () async {
                            TextEditingController controller =
                              new TextEditingController(text: todo.content);

                            Todo result = await showDialog(
                              context: context,
                              builder: (BuildContext context) {
                                return AlertDialog(
                                  title: Text('${todo.id} : ${todo.title}'),
                                  content: TextField(
                                    controller: controller,
                                    keyboardType: TextInputType.text,
                                  ),
                                  actions: <Widget>[
                                    TextButton(
                                      onPressed: () {
                                        todo.active == 1
                                            ? todo.active = 0
                                            : todo.active = 1;
                                        todo.content = 
                                            controller.value.text;
                                        Navigator.of(context).pop(todo);
                                      },
                                      child: Text('예')),

👉🏻 알림창 속의 내용을 수정하도록 텍스트 필드를 넣어 TextEditingController를 선언한다.

 

데이터 삭제하기
                          onLongPress: () async {
                            Todo result = await showDialog(
                              context: context,
                              builder: (BuildContext context) {
                                return AlertDialog(
                                  title: Text('${todo.id} : ${todo.title}'),
                                  content: Text('${todo.content}를 삭제하시겠습니까?'),
                                  actions: <Widget>[
                                    TextButton(
                                      onPressed: () {
                                        Navigator.of(context).pop(todo);
                                      },
                                      child: Text('예')),
                                    TextButton(
                                      onPressed: () {
                                        Navigator.of(context).pop();
                                      },
                                      child: Text('아니요')),
                                  ],
                                );
                              }
                            );
                            _deleteTodo(result);
                          },
                   
(...생략...)

  void _deleteTodo(Todo todo) async {
    final Database database = await widget.db;
    await database.delete('todos', where: 'id=?', whereArgs: [todo.id]);
    setState(() {
      todoList = getTodos();
    });
  }

👉🏻 길게 누르면 삭제하는 _deleteTodo 함수가 발생한다.

✍🏻 onLongPress: 길게 터치할 때 발생하는 이벤트

✍🏻 getTodos 함수로 현재 목록을 새로 고침 한다.

728x90