Dandy Now!
  • [Flutter] "Do it! 플러터 앱 프로그래밍" - 데이터베이스에 데이터 저장하기 | 데이터베이스 준비, 데이터 처리, 질의문 작성
    2022년 02월 18일 01시 55분 55초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    "조준수. (2021). Do it! 플러터 앱 프로그래밍. 이지스퍼블리싱", 10장을 실습하였다. 데이터베이스를 앱과 연동하는 방법이다. DBMS로는 SQLite를 사용하였다. sqflite 패키지를 이용해 Map 형태로 데이터를 CRUD 하는 방식과 query를 이용해 검색, 삭제, 수정하는 방식을 다루었다.

     

    Do it! 플러터 앱 프로그래밍

    플러터 기본 & 고급 위젯은 물론오픈 API와 파이어베이스를 이용한 앱 개발부터 배포까지!플러터 SDK 2.x 버전을 반영한 개정판!이 책은 플러터의 기초부터 고급 활용법까지 다루어 다양한 영역에

    book.naver.com

     

    데이터베이스 준비

    sqflite 패키지 설치

    pub.dev에서 SQLite 사용을 위한 sqflite 패키지를 찾아 설치한다(https://pub.dev/packages/sqflite/install).

    flutter pub add sqflite

     

    path 패키지 설치

    pub.dev에서 내부 저장소 사용을 위한 path 패키지를 찾아 설치한다(https://pub.dev/packages/path/install).

    flutter pub add path

     

    데이터베이스 만들기

    // main.dart
    import 'package:flutter/material.dart';
    import 'package:sqflite/sqflite.dart';
    import 'package:path/path.dart';
    import 'addTodo.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // 데이터베이스를 생성하는 함수 호출
        Future<Database> database = initDatabase(); 
    
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          initialRoute: '/',
          routes: {
            '/': (context) => DatabaseApp(database),
            '/add': (context) => AddTodoApp(database)
          },
        );
      }
    
      // 데이터베이스를 생성하는 함수
      Future<Database> initDatabase() async {
        return openDatabase(
          join(await getDatabasesPath(), 'todo_database.db'),
          // todo_database.db 파일에 테이블이 없으면 테이블을 만든다.
          onCreate: (db, version) => db.execute(
            "CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT, "
            "title TEXT, content TEXT, active INTEGER)",
          ),
          version: 1,
        );
      }
    }
    
    class DatabaseApp extends StatefulWidget {
      final Future<Database> db;
      DatabaseApp(this.db);
    
      @override
      State<StatefulWidget> createState() => _DatabaseApp();
    }
    
    class _DatabaseApp extends State<DatabaseApp> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Database Example'),
          ),
          body: Container(),
          floatingActionButton: FloatingActionButton(
            onPressed: () async {
              final todo = await Navigator.of(context).pushNamed('/add');
            },
            child: const Icon(Icons.add),
          ),
          floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
        );
      }
    }

     

    // todo.dart
    class Todo {
      String? title;
      String? content;
      int? active;
      int? id;
      Todo({this.title, this.content, this.active, this.id});
    
      Map<String, dynamic> toMap() {
        // toMap 함수는 데이터를 Map 형태로 변환한다. sqflite 패키지는 데이터를 Map 형태로 다룬다.
        return {
          'id': id,
          'title': title,
          'content': content,
          'active': active,
        };
      }
    }

     

    // addTodo.dart
    import 'package:flutter/material.dart';
    import 'package:sqflite/sqlite_api.dart';
    import 'todo.dart';
    
    class AddTodoApp extends StatefulWidget {
      final Future<Database> db;
      AddTodoApp(this.db);
    
      @override
      State<StatefulWidget> createState() => _AddTodoApp();
    }
    
    class _AddTodoApp extends State<AddTodoApp> {
      @override
      void initState() {
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Todo 추가'),
          ),
          body: Container(
            child: Center(
              child: Column(
                children: <Widget>[],
              ),
            ),
          ),
        );
      }
    }

     


     

    데이터베이스에서 데이터 처리하기

    데이터 입력하기

    // main.dart
    import 'package:flutter/material.dart';
    import 'package:sqflite/sqflite.dart';
    import 'package:path/path.dart';
    import 'addTodo.dart';
    import 'todo.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // 데이터베이스를 생성하는 함수 호출
        Future<Database> database = initDatabase();
    
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          initialRoute: '/',
          routes: {
            '/': (context) => DatabaseApp(database),
            '/add': (context) => AddTodoApp(database)
          },
        );
      }
      // home: DatabaseApp(),
      // );
      // }
    
      // 데이터베이스를 생성하는 함수
      Future<Database> initDatabase() async {
        return openDatabase(
          join(await getDatabasesPath(), 'todo_database.db'),
          // todo_database.db 파일에 테이블이 없으면 테이블을 만든다.
          onCreate: (db, version) => db.execute(
            "CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT, "
            "title TEXT, content TEXT, active INTEGER)",
          ),
          version: 1,
        );
      }
    }
    
    class DatabaseApp extends StatefulWidget {
      final Future<Database> db;
      DatabaseApp(this.db);
    
      @override
      State<StatefulWidget> createState() => _DatabaseApp();
    }
    
    class _DatabaseApp extends State<DatabaseApp> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Database Example'),
          ),
          body: Container(),
          floatingActionButton: FloatingActionButton(
            onPressed: () async {
              final todo = await Navigator.of(context).pushNamed('/add');
              if (todo != null) {
                _insertTodo(todo as Todo); // 데이터 입력하기 함수 호출
              }
            },
            child: const Icon(Icons.add),
          ), // This trailing comma makes auto-formatting nicer for build methods.
          floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
        );
      }
    
      // 데이터 입력하기 함수
      void _insertTodo(Todo todo) async {
        final Database database = await widget.db;
        await database.insert('todos', todo.toMap(),
            conflictAlgorithm:
                ConflictAlgorithm.replace); // 충돌이 발생하면 새 데이터로 교체하고자 replace로 선언함
      }
    }

     

    // addTodo.dart
    import 'package:flutter/material.dart';
    import 'package:sqflite/sqlite_api.dart';
    import 'todo.dart';
    
    class AddTodoApp extends StatefulWidget {
      final Future<Database> db;
      AddTodoApp(this.db);
    
      @override
      State<StatefulWidget> createState() => _AddTodoApp();
    }
    
    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: '할일'),
                    ),
                  ),
                  // 버튼을 눌렀을 때 pop 함수를 호출하면서 데이터를 메인에 전달한다.
                  ElevatedButton(
                    onPressed: () {
                      Todo todo = Todo(
                          title: titleController!.value.text,
                          content: contentController!.value.text,
                          active: 0);
                      Navigator.of(context).pop(todo);
                    },
                    child: Text('저장하기'),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }

     

    [그림 1] 데이터 입력하기

     


     

    데이터 검색하기

    // main.dart
    import 'package:flutter/material.dart';
    import 'package:sqflite/sqflite.dart';
    import 'package:path/path.dart';
    import 'addTodo.dart';
    import 'todo.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // 데이터베이스를 생성하는 함수 호출
        Future<Database> database = initDatabase();
    
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          initialRoute: '/',
          routes: {
            '/': (context) => DatabaseApp(database),
            '/add': (context) => AddTodoApp(database)
          },
        );
      }
      // home: DatabaseApp(),
      // );
      // }
    
      // 데이터베이스를 생성하는 함수
      Future<Database> initDatabase() async {
        return openDatabase(
          join(await getDatabasesPath(), 'todo_database.db'),
          // todo_database.db 파일에 테이블이 없으면 테이블을 만든다.
          onCreate: (db, version) {
            return db.execute(
              "CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT, "
              "title TEXT, content TEXT, active INTEGER)",
            );
          },
          version: 1,
        );
      }
    }
    
    class DatabaseApp extends StatefulWidget {
      final Future<Database> db;
      DatabaseApp(this.db);
    
      @override
      State<StatefulWidget> createState() => _DatabaseApp();
    }
    
    class _DatabaseApp extends State<DatabaseApp> {
      Future<List<Todo>>? todoList;
    
      // Future로 선언할 할 일 목록은 계속 값이 변하므로 따로 변수를 선언해 initState 함수에서 호출하도록 한다.
      @override
      void initState() {
        super.initState();
        todoList = getTodos();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Database Example'),
          ),
          body: Container(
            child: Center(
              // FutureBuilder 위젯은 서버에서 데이터를 받거나 파일에 데이터를 가져올 때 사용한다.
              // 데이터를 가져오는 동안에 시간이 걸리기 때문에 그 사이에 표시할 위젯을 만들기 위해 switch문으로 snapshot.connectionState를 이용해 지금 상태를 확인한다.
              // 상태가 done이 되면 가져온 데이터를 바탕으로 ListView.builder를 이용해 화면에 표시한다.
              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,
              ),
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () async {
              final todo = await Navigator.of(context).pushNamed('/add');
              if (todo != null) {
                _insertTodo(todo as Todo); // 데이터 입력하기 함수 호출
              }
            },
            child: const Icon(Icons.add),
          ), // This trailing comma makes auto-formatting nicer for build methods.
          floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
        );
      }
    
      // 데이터 입력하기 함수
      void _insertTodo(Todo todo) async {
        final Database database = await widget.db;
        await database.insert('todos', todo.toMap(),
            conflictAlgorithm:
                ConflictAlgorithm.replace); // 충돌이 발생하면 새 데이터로 교체하고자 replace로 선언함
        // todoList를 갱신하는 코드
        setState(() {
          todoList = getTodos();
        });
      }
    
      // 데이터베이스에서 데이터를 가져오는 함수
      // await widget.db에 오류가 있었다.
      // path 패키지를 설치하고, 함수 위치를 _insertTodo 함수 아래로 내렸더니 해결되었다.
      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'].toString(),
              active: active,
              id: maps[i]['id']);
        });
      }
    }

     

    [그림 2] 데이터 검색하기

     


     

    데이터 수정하기

    알림창을 통해 [그림 3]과 같이 체크를 true / false로 변경하는 기능을 구현한다.

    // main.dart
    import 'package:flutter/material.dart';
    import 'package:sqflite/sqflite.dart';
    import 'package:path/path.dart';
    import 'addTodo.dart';
    import 'todo.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // 데이터베이스를 생성하는 함수 호출
        Future<Database> database = initDatabase();
    
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          initialRoute: '/',
          routes: {
            '/': (context) => DatabaseApp(database),
            '/add': (context) => AddTodoApp(database)
          },
        );
      }
      // home: DatabaseApp(),
      // );
      // }
    
      // 데이터베이스를 생성하는 함수
      Future<Database> initDatabase() async {
        return openDatabase(
          join(await getDatabasesPath(), 'todo_database.db'),
          // todo_database.db 파일에 테이블이 없으면 테이블을 만든다.
          onCreate: (db, version) {
            return db.execute(
              "CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT, "
              "title TEXT, content TEXT, active INTEGER)",
            );
          },
          version: 1,
        );
      }
    }
    
    class DatabaseApp extends StatefulWidget {
      final Future<Database> db;
      DatabaseApp(this.db);
    
      @override
      State<StatefulWidget> createState() => _DatabaseApp();
    }
    
    class _DatabaseApp extends State<DatabaseApp> {
      Future<List<Todo>>? todoList;
    
      // Future로 선언할 할 일 목록은 계속 값이 변하므로 따로 변수를 선언해 initState 함수에서 호출하도록 한다.
      @override
      void initState() {
        super.initState();
        todoList = getTodos();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Database Example'),
          ),
          body: Container(
            child: Center(
              // FutureBuilder 위젯은 서버에서 데이터를 받거나 파일에 데이터를 가져올 때 사용한다.
              // 데이터를 가져오는 동안에 시간이 걸리기 때문에 그 사이에 표시할 위젯을 만들기 위해 switch문으로 snapshot.connectionState를 이용해 지금 상태를 확인한다.
              // 상태가 done이 되면 가져온 데이터를 바탕으로 ListView.builder를 이용해 화면에 표시한다.
              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) {
                        // Card 위젯이 칸막이를 만들고 그 안에서 위젯을 자유롭게 꾸밀 수 있었다면, ListTile은 title, subtitle, leading, trailing 옵션으로 위젯의 위치를 지정할 수 있다.
                        return ListView.builder(
                          itemBuilder: (context, index) {
                            Todo todo = (snapshot.data as List<Todo>)[index];
                            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,
                                    )
                                  ],
                                ),
                              ),
                              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);
                              },
                            );
                          },
                          itemCount: (snapshot.data as List<Todo>).length,
                        );
                      } else {
                        return Text('No data');
                      }
                  }
                  return CircularProgressIndicator();
                },
                future: todoList,
              ),
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () async {
              final todo = await Navigator.of(context).pushNamed('/add');
              if (todo != null) {
                _insertTodo(todo as Todo); // 데이터 입력하기 함수 호출
              }
            },
            child: const Icon(Icons.add),
          ), // This trailing comma makes auto-formatting nicer for build methods.
          floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
        );
      }
    
      // 데이터를 수정하기 함수
      void _updateTodo(Todo todo) async {
        final Database database = await widget.db;
        await database.update(
          'todos',
          todo.toMap(),
          // id값으로 수정할 데이터 찾기
          where: 'id = ?',
          whereArgs: [todo.id],
        );
        setState(() {
          todoList = getTodos();
        });
      }
    
      // 데이터 입력하기 함수
      void _insertTodo(Todo todo) async {
        final Database database = await widget.db;
        await database.insert('todos', todo.toMap(),
            conflictAlgorithm:
                ConflictAlgorithm.replace); // 충돌이 발생하면 새 데이터로 교체하고자 replace로 선언함
        // todoList를 갱신하는 코드
        setState(() {
          todoList = getTodos();
        });
      }
    
      // 데이터베이스에서 데이터를 가져오는 함수
      // await widget.db에 오류가 있었다.
      // path 패키지를 설치하고, 함수 위치를 _insertTodo 함수 아래로 내렸더니 해결되었다.
      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'].toString(),
              active: active,
              id: maps[i]['id']);
        });
      }
    }

     

    [그림 3] 체크 true / false 기능

     

    알림 창에 텍스트 필드를 추가하여 [그림 4]와 같이 할 일 내용 수정 기능을 구현한다.

    // main.dart
    import 'package:flutter/material.dart';
    import 'package:sqflite/sqflite.dart';
    import 'package:path/path.dart';
    import 'addTodo.dart';
    import 'todo.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // 데이터베이스를 생성하는 함수 호출
        Future<Database> database = initDatabase();
    
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          initialRoute: '/',
          routes: {
            '/': (context) => DatabaseApp(database),
            '/add': (context) => AddTodoApp(database)
          },
        );
      }
      // home: DatabaseApp(),
      // );
      // }
    
      // 데이터베이스를 생성하는 함수
      Future<Database> initDatabase() async {
        return openDatabase(
          join(await getDatabasesPath(), 'todo_database.db'),
          // todo_database.db 파일에 테이블이 없으면 테이블을 만든다.
          onCreate: (db, version) {
            return db.execute(
              "CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT, "
              "title TEXT, content TEXT, active INTEGER)",
            );
          },
          version: 1,
        );
      }
    }
    
    class DatabaseApp extends StatefulWidget {
      final Future<Database> db;
      DatabaseApp(this.db);
    
      @override
      State<StatefulWidget> createState() => _DatabaseApp();
    }
    
    class _DatabaseApp extends State<DatabaseApp> {
      Future<List<Todo>>? todoList;
    
      // Future로 선언할 할 일 목록은 계속 값이 변하므로 따로 변수를 선언해 initState 함수에서 호출하도록 한다.
      @override
      void initState() {
        super.initState();
        todoList = getTodos();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Database Example'),
          ),
          body: Container(
            child: Center(
              // FutureBuilder 위젯은 서버에서 데이터를 받거나 파일에 데이터를 가져올 때 사용한다.
              // 데이터를 가져오는 동안에 시간이 걸리기 때문에 그 사이에 표시할 위젯을 만들기 위해 switch문으로 snapshot.connectionState를 이용해 지금 상태를 확인한다.
              // 상태가 done이 되면 가져온 데이터를 바탕으로 ListView.builder를 이용해 화면에 표시한다.
              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) {
                        // Card 위젯이 칸막이를 만들고 그 안에서 위젯을 자유롭게 꾸밀 수 있었다면, ListTile은 title, subtitle, leading, trailing 옵션으로 위젯의 위치를 지정할 수 있다.
                        return ListView.builder(
                          itemBuilder: (context, index) {
                            Todo todo = (snapshot.data as List<Todo>)[index];
                            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,
                                    )
                                  ],
                                ),
                              ),
                              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('예')),
                                          TextButton(
                                              onPressed: () {
                                                Navigator.of(context).pop(todo);
                                              },
                                              child: Text('아니요')),
                                        ],
                                      );
                                    });
                                _updateTodo(result);
                              },
                            );
                          },
                          itemCount: (snapshot.data as List<Todo>).length,
                        );
                      } else {
                        return Text('No data');
                      }
                  }
                  return CircularProgressIndicator();
                },
                future: todoList,
              ),
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () async {
              final todo = await Navigator.of(context).pushNamed('/add');
              if (todo != null) {
                _insertTodo(todo as Todo); // 데이터 입력하기 함수 호출
              }
            },
            child: const Icon(Icons.add),
          ), // This trailing comma makes auto-formatting nicer for build methods.
          floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
        );
      }
    
      // 데이터를 수정하기 함수
      void _updateTodo(Todo todo) async {
        final Database database = await widget.db;
        await database.update(
          'todos',
          todo.toMap(),
          // id값으로 수정할 데이터 찾기
          where: 'id = ?',
          whereArgs: [todo.id],
        );
        setState(() {
          todoList = getTodos();
        });
      }
    
      // 데이터 입력하기 함수
      void _insertTodo(Todo todo) async {
        final Database database = await widget.db;
        await database.insert('todos', todo.toMap(),
            conflictAlgorithm:
                ConflictAlgorithm.replace); // 충돌이 발생하면 새 데이터로 교체하고자 replace로 선언함
        // todoList를 갱신하는 코드
        setState(() {
          todoList = getTodos();
        });
      }
    
      // 데이터베이스에서 데이터를 가져오는 함수
      // await widget.db에 오류가 있었다.
      // path 패키지를 설치하고, 함수 위치를 _insertTodo 함수 아래로 내렸더니 해결되었다.
      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'].toString(),
              active: active,
              id: maps[i]['id']);
        });
      }
    }

     

    [그림 4] 데이터 수정하기

     


     

    데이터 삭제하기

    import 'package:flutter/material.dart';
    import 'package:sqflite/sqflite.dart';
    import 'package:path/path.dart';
    import 'addTodo.dart';
    import 'todo.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // 데이터베이스를 생성하는 함수 호출
        Future<Database> database = initDatabase();
    
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          initialRoute: '/',
          routes: {
            '/': (context) => DatabaseApp(database),
            '/add': (context) => AddTodoApp(database)
          },
        );
      }
      // home: DatabaseApp(),
      // );
      // }
    
      // 데이터베이스를 생성하는 함수
      Future<Database> initDatabase() async {
        return openDatabase(
          join(await getDatabasesPath(), 'todo_database.db'),
          // todo_database.db 파일에 테이블이 없으면 테이블을 만든다.
          onCreate: (db, version) {
            return db.execute(
              "CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT, "
              "title TEXT, content TEXT, active INTEGER)",
            );
          },
          version: 1,
        );
      }
    }
    
    class DatabaseApp extends StatefulWidget {
      final Future<Database> db;
      DatabaseApp(this.db);
    
      @override
      State<StatefulWidget> createState() => _DatabaseApp();
    }
    
    class _DatabaseApp extends State<DatabaseApp> {
      Future<List<Todo>>? todoList;
    
      // Future로 선언할 할 일 목록은 계속 값이 변하므로 따로 변수를 선언해 initState 함수에서 호출하도록 한다.
      @override
      void initState() {
        super.initState();
        todoList = getTodos();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Database Example'),
          ),
          body: Container(
            child: Center(
              // FutureBuilder 위젯은 서버에서 데이터를 받거나 파일에 데이터를 가져올 때 사용한다.
              // 데이터를 가져오는 동안에 시간이 걸리기 때문에 그 사이에 표시할 위젯을 만들기 위해 switch문으로 snapshot.connectionState를 이용해 지금 상태를 확인한다.
              // 상태가 done이 되면 가져온 데이터를 바탕으로 ListView.builder를 이용해 화면에 표시한다.
              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) {
                        // Card 위젯이 칸막이를 만들고 그 안에서 위젯을 자유롭게 꾸밀 수 있었다면, ListTile은 title, subtitle, leading, trailing 옵션으로 위젯의 위치를 지정할 수 있다.
                        return ListView.builder(
                          itemBuilder: (context, index) {
                            Todo todo = (snapshot.data as List<Todo>)[index];
                            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,
                                    )
                                  ],
                                ),
                              ),
                              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('예')),
                                          TextButton(
                                              onPressed: () {
                                                Navigator.of(context).pop(todo);
                                              },
                                              child: Text('아니요')),
                                        ],
                                      );
                                    });
                                _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}를(을) 삭제하시겠습니까?'),
                                        actions: <Widget>[
                                          TextButton(
                                              onPressed: () {
                                                Navigator.of(context).pop(todo);
                                              },
                                              child: Text('예')),
                                          TextButton(
                                              onPressed: () {
                                                Navigator.of(context).pop();
                                              },
                                              child: Text('아니오')),
                                        ],
                                      );
                                    });
                                _deleteTodo(result);
                              },
                            );
                          },
                          itemCount: (snapshot.data as List<Todo>).length,
                        );
                      } else {
                        return Text('No data');
                      }
                  }
                  return CircularProgressIndicator();
                },
                future: todoList,
              ),
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () async {
              final todo = await Navigator.of(context).pushNamed('/add');
              if (todo != null) {
                _insertTodo(todo as Todo); // 데이터 입력하기 함수 호출
              }
            },
            child: const Icon(Icons.add),
          ), // This trailing comma makes auto-formatting nicer for build methods.
          floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
        );
      }
    
      // 데이터 삭제하기 함수
      void _deleteTodo(Todo todo) async {
        final Database database = await widget.db;
        await database.delete('todos', where: 'id=?', whereArgs: [todo.id]);
        setState(() {
          todoList = getTodos();
        });
      }
    
      // 데이터 수정하기 함수
      void _updateTodo(Todo todo) async {
        final Database database = await widget.db;
        await database.update(
          'todos',
          todo.toMap(),
          // id값으로 수정할 데이터 찾기
          where: 'id = ?',
          whereArgs: [todo.id],
        );
        setState(() {
          todoList = getTodos();
        });
      }
    
      // 데이터 입력하기 함수
      void _insertTodo(Todo todo) async {
        final Database database = await widget.db;
        await database.insert('todos', todo.toMap(),
            conflictAlgorithm:
                ConflictAlgorithm.replace); // 충돌이 발생하면 새 데이터로 교체하고자 replace로 선언함
        // todoList를 갱신하는 코드
        setState(() {
          todoList = getTodos();
        });
      }
    
      // 데이터베이스에서 데이터를 가져오는 함수
      // await widget.db에 오류가 있었다.
      // path 패키지를 설치하고, 함수 위치를 _insertTodo 함수 아래로 내렸더니 해결되었다.
      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'].toString(),
              active: active,
              id: maps[i]['id']);
        });
      }
    }

     

    [그림 5] 데이터 삭제하기

     


     

    질의문으로 추가 기능 만들기

    완료한 일만 모아서 보기 - 검색 질의

    Map 형태가 아니라 query를 작성해서 데이터를 처리하는 방법이다. 실습 중 [그림 6]과 같은 무한 로딩 현상이 발생해 애를 먹었다. 원인은 CircularProgressIndicator 함수를 빠져나오는 부분이 없었기 때문이다.

    // main.dart
    import 'package:flutter/material.dart';
    import 'package:sqflite/sqflite.dart';
    import 'package:path/path.dart';
    import 'addTodo.dart';
    import 'todo.dart';
    import 'clearList.dart'; // 완료한 일만 보여주는 클래스
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // 데이터베이스를 생성하는 함수 호출
        Future<Database> database = initDatabase();
    
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          initialRoute: '/',
          routes: {
            '/': (context) => DatabaseApp(database),
            '/add': (context) => AddTodoApp(database),
            '/clear': (context) => ClearListApp(database) // 완료한 일만 보여주는 클래스 경로
          },
        );
      }
      // home: DatabaseApp(),
      // );
      // }
    
      // 데이터베이스를 생성하는 함수
      Future<Database> initDatabase() async {
        return openDatabase(
          join(await getDatabasesPath(), 'todo_database.db'),
          // todo_database.db 파일에 테이블이 없으면 테이블을 만든다.
          onCreate: (db, version) {
            return db.execute(
              "CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT, "
              "title TEXT, content TEXT, active INTEGER)",
            );
          },
          version: 1,
        );
      }
    }
    
    class DatabaseApp extends StatefulWidget {
      final Future<Database> db;
      DatabaseApp(this.db);
    
      @override
      State<StatefulWidget> createState() => _DatabaseApp();
    }
    
    class _DatabaseApp extends State<DatabaseApp> {
      Future<List<Todo>>? todoList;
    
      // Future로 선언할 할 일 목록은 계속 값이 변하므로 따로 변수를 선언해 initState 함수에서 호출하도록 한다.
      @override
      void initState() {
        super.initState();
        todoList = getTodos();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Database Example'),
            actions: <Widget>[
              // 버튼을 눌렀을 때 ClearListApp으로 이동하는 코드
              TextButton(
                  onPressed: () async {
                    await Navigator.of(context).pushNamed('/clear');
                    setState(() {
                      todoList = getTodos();
                    });
                  },
                  child: Text(
                    '완료한 일',
                    style: TextStyle(color: Colors.white),
                  ))
            ],
          ),
          body: Container(
            child: Center(
              // FutureBuilder 위젯은 서버에서 데이터를 받거나 파일에 데이터를 가져올 때 사용한다.
              // 데이터를 가져오는 동안에 시간이 걸리기 때문에 그 사이에 표시할 위젯을 만들기 위해 switch문으로 snapshot.connectionState를 이용해 지금 상태를 확인한다.
              // 상태가 done이 되면 가져온 데이터를 바탕으로 ListView.builder를 이용해 화면에 표시한다.
              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) {
                        // Card 위젯이 칸막이를 만들고 그 안에서 위젯을 자유롭게 꾸밀 수 있었다면, ListTile은 title, subtitle, leading, trailing 옵션으로 위젯의 위치를 지정할 수 있다.
                        return ListView.builder(
                          itemBuilder: (context, index) {
                            Todo todo = (snapshot.data as List<Todo>)[index];
                            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,
                                    )
                                  ],
                                ),
                              ),
                              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('예')),
                                          TextButton(
                                              onPressed: () {
                                                Navigator.of(context).pop(todo);
                                              },
                                              child: Text('아니요')),
                                        ],
                                      );
                                    });
                                _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}를(을) 삭제하시겠습니까?'),
                                        actions: <Widget>[
                                          TextButton(
                                              onPressed: () {
                                                Navigator.of(context).pop(todo);
                                              },
                                              child: Text('예')),
                                          TextButton(
                                              onPressed: () {
                                                Navigator.of(context).pop();
                                              },
                                              child: Text('아니오')),
                                        ],
                                      );
                                    });
                                _deleteTodo(result);
                              },
                            );
                          },
                          itemCount: (snapshot.data as List<Todo>).length,
                        );
                      } else {
                        return Text('No data');
                      }
                  }
                  return CircularProgressIndicator();
                },
                future: todoList,
              ),
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () async {
              final todo = await Navigator.of(context).pushNamed('/add');
              if (todo != null) {
                _insertTodo(todo as Todo); // 데이터 입력하기 함수 호출
              }
            },
            child: const Icon(Icons.add),
          ), // This trailing comma makes auto-formatting nicer for build methods.
          floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
        );
      }
    
      // 데이터 삭제하기 함수
      void _deleteTodo(Todo todo) async {
        final Database database = await widget.db;
        await database.delete('todos', where: 'id=?', whereArgs: [todo.id]);
        setState(() {
          todoList = getTodos();
        });
      }
    
      // 데이터 수정하기 함수
      void _updateTodo(Todo todo) async {
        final Database database = await widget.db;
        await database.update(
          'todos',
          todo.toMap(),
          // id값으로 수정할 데이터 찾기
          where: 'id = ?',
          whereArgs: [todo.id],
        );
        setState(() {
          todoList = getTodos();
        });
      }
    
      // 데이터 입력하기 함수
      void _insertTodo(Todo todo) async {
        final Database database = await widget.db;
        await database.insert('todos', todo.toMap(),
            conflictAlgorithm:
                ConflictAlgorithm.replace); // 충돌이 발생하면 새 데이터로 교체하고자 replace로 선언함
        // todoList를 갱신하는 코드
        setState(() {
          todoList = getTodos();
        });
      }
    
      // 데이터베이스에서 데이터를 가져오는 함수
      // await widget.db에 오류가 있었다.
      // path 패키지를 설치하고, 함수 위치를 _insertTodo 함수 아래로 내렸더니 해결되었다.
      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'].toString(),
              active: active,
              id: maps[i]['id']);
        });
      }
    }

     

    // clearList.dart
    import 'package:flutter/material.dart';
    import 'package:sqflite/sqlite_api.dart';
    import 'todo.dart';
    
    class ClearListApp extends StatefulWidget {
      Future<Database> database;
      ClearListApp(this.database);
    
      @override
      State<StatefulWidget> createState() => _ClearListApp();
    }
    
    class _ClearListApp extends State<ClearListApp> {
      Future<List<Todo>>? clearList;
    
      @override
      void initState() {
        super.initState();
        clearList = getClearList();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('완료한 일'),
          ),
          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:
                      // return CircularProgressIndicator(); // [그림 6] 무한 로딩의 원인이었음
                      if (snapshot.hasData) {
                        return ListView.builder(
                          itemBuilder: (context, index) {
                            Todo todo = (snapshot.data as List<Todo>)[index];
                            return ListTile(
                              title: Text(
                                todo.title!,
                                style: TextStyle(fontSize: 20),
                              ),
                              subtitle: Container(
                                child: Column(
                                  children: <Widget>[
                                    Text(todo.content!),
                                    Container(
                                      height: 1,
                                      color: Colors.blue,
                                    )
                                  ],
                                ),
                              ),
                            );
                          },
                          itemCount: (snapshot.data as List<Todo>).length,
                        );
                      }
                  }
                  return Text('No data');
                },
                future: clearList,
              ),
            ),
          ),
        );
      }
    
      Future<List<Todo>> getClearList() async {
        final Database database = await widget.database;
        List<Map<String, dynamic>> maps = await database.rawQuery(
            'select title, content, id from todos where active=1'); // 완료한 일(active가 1인 경우)만 가져오는 query
    
        return List.generate(maps.length, (i) {
          return Todo(
              title: maps[i]['title'].toString(),
              content: maps[i]['content'].toString(),
              id: maps[i]['id']);
        });
      }
    }

     

    [그림 6] 무한 로딩 현상

     

    [그림 7] 완료한 일

     

    완료한 일 모두 지우기 - 삭제 질의

    // clearList.dart
    import 'package:flutter/material.dart';
    import 'package:sqflite/sqlite_api.dart';
    import 'todo.dart';
    
    class ClearListApp extends StatefulWidget {
      Future<Database> database;
      ClearListApp(this.database);
    
      @override
      State<StatefulWidget> createState() => _ClearListApp();
    }
    
    class _ClearListApp extends State<ClearListApp> {
      Future<List<Todo>>? clearList;
    
      @override
      void initState() {
        super.initState();
        clearList = getClearList();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('완료한 일'),
          ),
          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:
                      // return CircularProgressIndicator(); // 무한 로딩의 원인이었음
                      if (snapshot.hasData) {
                        return ListView.builder(
                          itemBuilder: (context, index) {
                            Todo todo = (snapshot.data as List<Todo>)[index];
                            return ListTile(
                              title: Text(
                                todo.title!,
                                style: TextStyle(fontSize: 20),
                              ),
                              subtitle: Container(
                                child: Column(
                                  children: <Widget>[
                                    Text(todo.content!),
                                    Container(
                                      height: 1,
                                      color: Colors.blue,
                                    )
                                  ],
                                ),
                              ),
                            );
                          },
                          itemCount: (snapshot.data as List<Todo>).length,
                        );
                      }
                  }
                  return Text('No data');
                },
                future: clearList,
              ),
            ),
          ),
          // 완료한 일 삭제 버튼
          floatingActionButton: FloatingActionButton(
            onPressed: () async {
              final result = await showDialog(
                  context: context,
                  builder: (BuildContext context) {
                    return AlertDialog(
                      title: Text('완료한 일 삭제'),
                      content: Text('완료한 일을 모두 삭제할까요?'),
                      actions: <Widget>[
                        TextButton(
                            onPressed: () {
                              Navigator.of(context).pop(true);
                            },
                            child: Text('예')),
                        TextButton(
                            onPressed: () {
                              Navigator.of(context).pop(false);
                            },
                            child: Text('아니오')),
                      ],
                    );
                  });
              if (result == true) {
                _removeAllTodos();
              }
            },
            child: Icon(Icons.remove),
          ),
        );
      }
    
      void _removeAllTodos() async {
        final Database database = await widget.database;
        database.rawDelete(
            'delete from todos where active=1'); // 완료한 일(active가 1인 경우)만 삭제하는 query
        setState(() {
          clearList = getClearList();
        });
      }
    
      Future<List<Todo>> getClearList() async {
        final Database database = await widget.database;
        List<Map<String, dynamic>> maps = await database.rawQuery(
            'select title, content, id from todos where active=1'); // 완료한 일(active가 1인 경우)만 가져오는 query
    
        return List.generate(maps.length, (i) {
          return Todo(
              title: maps[i]['title'].toString(),
              content: maps[i]['content'].toString(),
              id: maps[i]['id']);
        });
      }
    }

     

    [그림 8] 완료한 일 삭제

     

    모두 완료로 수정하기 - 수정 질의

    // main.dart
    import 'package:flutter/material.dart';
    import 'package:sqflite/sqflite.dart';
    import 'package:path/path.dart';
    import 'addTodo.dart';
    import 'todo.dart';
    import 'clearList.dart'; // 완료한 일만 보여주는 클래스
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // 데이터베이스를 생성하는 함수 호출
        Future<Database> database = initDatabase();
    
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          initialRoute: '/',
          routes: {
            '/': (context) => DatabaseApp(database),
            '/add': (context) => AddTodoApp(database),
            '/clear': (context) => ClearListApp(database) // 완료한 일만 보여주는 클래스 경로
          },
        );
      }
      // home: DatabaseApp(),
      // );
      // }
    
      // 데이터베이스를 생성하는 함수
      Future<Database> initDatabase() async {
        return openDatabase(
          join(await getDatabasesPath(), 'todo_database.db'),
          // todo_database.db 파일에 테이블이 없으면 테이블을 만든다.
          onCreate: (db, version) {
            return db.execute(
              "CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT, "
              "title TEXT, content TEXT, active INTEGER)",
            );
          },
          version: 1,
        );
      }
    }
    
    class DatabaseApp extends StatefulWidget {
      final Future<Database> db;
      DatabaseApp(this.db);
    
      @override
      State<StatefulWidget> createState() => _DatabaseApp();
    }
    
    class _DatabaseApp extends State<DatabaseApp> {
      Future<List<Todo>>? todoList;
    
      // Future로 선언할 할 일 목록은 계속 값이 변하므로 따로 변수를 선언해 initState 함수에서 호출하도록 한다.
      @override
      void initState() {
        super.initState();
        todoList = getTodos();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text('Database Example'),
              actions: <Widget>[
                // 버튼을 눌렀을 때 ClearListApp으로 이동하는 코드
                TextButton(
                    onPressed: () async {
                      await Navigator.of(context).pushNamed('/clear');
                      setState(() {
                        todoList = getTodos();
                      });
                    },
                    child: Text(
                      '완료한 일',
                      style: TextStyle(color: Colors.white),
                    ))
              ],
            ),
            body: Container(
              child: Center(
                // FutureBuilder 위젯은 서버에서 데이터를 받거나 파일에 데이터를 가져올 때 사용한다.
                // 데이터를 가져오는 동안에 시간이 걸리기 때문에 그 사이에 표시할 위젯을 만들기 위해 switch문으로 snapshot.connectionState를 이용해 지금 상태를 확인한다.
                // 상태가 done이 되면 가져온 데이터를 바탕으로 ListView.builder를 이용해 화면에 표시한다.
                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) {
                          // Card 위젯이 칸막이를 만들고 그 안에서 위젯을 자유롭게 꾸밀 수 있었다면, ListTile은 title, subtitle, leading, trailing 옵션으로 위젯의 위치를 지정할 수 있다.
                          return ListView.builder(
                            itemBuilder: (context, index) {
                              Todo todo = (snapshot.data as List<Todo>)[index];
                              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,
                                      )
                                    ],
                                  ),
                                ),
                                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('예')),
                                            TextButton(
                                                onPressed: () {
                                                  Navigator.of(context).pop(todo);
                                                },
                                                child: Text('아니요')),
                                          ],
                                        );
                                      });
                                  _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}를(을) 삭제하시겠습니까?'),
                                          actions: <Widget>[
                                            TextButton(
                                                onPressed: () {
                                                  Navigator.of(context).pop(todo);
                                                },
                                                child: Text('예')),
                                            TextButton(
                                                onPressed: () {
                                                  Navigator.of(context).pop();
                                                },
                                                child: Text('아니오')),
                                          ],
                                        );
                                      });
                                  _deleteTodo(result);
                                },
                              );
                            },
                            itemCount: (snapshot.data as List<Todo>).length,
                          );
                        } else {
                          return Text('No data');
                        }
                    }
                    return CircularProgressIndicator();
                  },
                  future: todoList,
                ),
              ),
            ),
            floatingActionButton: Column(
              children: <Widget>[
                FloatingActionButton(
                  onPressed: () async {
                    final todo = await Navigator.of(context).pushNamed('/add');
                    if (todo != null) {
                      _insertTodo(todo as Todo); // 데이터 입력하기 함수 호출
                    }
                  },
                  heroTag: null,
                  child: Icon(Icons.add),
                ),
                SizedBox(
                  height: 10,
                ),
                FloatingActionButton(
                  onPressed: () async {
                    _allUpdate();
                  },
                  heroTag: null,
                  child: Icon(Icons.update),
                ),
              ],
              mainAxisAlignment: MainAxisAlignment.end,
            ));
      }
    
      void _allUpdate() async {
        final Database database = await widget.db;
        await database.rawUpdate('update todos set active=1 where active = 0'); // 모든 할 일 완료로 수정하는 query
        setState(() {
          todoList = getTodos();
        });
      }
    
      // 데이터 삭제하기 함수
      void _deleteTodo(Todo todo) async {
        final Database database = await widget.db;
        await database.delete('todos', where: 'id=?', whereArgs: [todo.id]);
        setState(() {
          todoList = getTodos();
        });
      }
    
      // 데이터 수정하기 함수
      void _updateTodo(Todo todo) async {
        final Database database = await widget.db;
        await database.update(
          'todos',
          todo.toMap(),
          // id값으로 수정할 데이터 찾기
          where: 'id = ?',
          whereArgs: [todo.id],
        );
        setState(() {
          todoList = getTodos();
        });
      }
    
      // 데이터 입력하기 함수
      void _insertTodo(Todo todo) async {
        final Database database = await widget.db;
        await database.insert('todos', todo.toMap(),
            conflictAlgorithm:
                ConflictAlgorithm.replace); // 충돌이 발생하면 새 데이터로 교체하고자 replace로 선언함
        // todoList를 갱신하는 코드
        setState(() {
          todoList = getTodos();
        });
      }
    
      // 데이터베이스에서 데이터를 가져오는 함수
      // await widget.db에 오류가 있었다.
      // path 패키지를 설치하고, 함수 위치를 _insertTodo 함수 아래로 내렸더니 해결되었다.
      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'].toString(),
              active: active,
              id: maps[i]['id']);
        });
      }
    }

     

    [그림 9] 모든 체크를 true로 수정

     

    728x90
    반응형
    댓글