본문 바로가기
버킷리스트/앱 만들기

오공완 (with Do it! 플러터 앱 프로그램밍) #10_1

by 또또도전 2024. 5. 12.
반응형

// 이곳에 쓴 내용은 앱 만들기라는 버킷리스트를 달성하기 위해 플러터를 공부하면서 정리하고 있는 내용입니다.

플러터에 대해 아는 것이 거의 없기 때문에 정리하면서 오류가 있을 수 있습니다.

오류를 발견하신 분은 댓글 남겨 주시면 감사하겠습니다.

 

   데이터베이스 데이터 저장하기

sqflite: ^2.3.3+1, path: ^1.9.0, pubspec.yaml에 추가

https://pub.dev/packages/sqflite

 

sqflite | Flutter package

Flutter plugin for SQLite, a self-contained, high-reliability, embedded, SQL database engine.

pub.dev

 

https://pub.dev/packages/path

 

path | Dart package

A string-based path manipulation library. All of the path operations you know and love, with solid support for Windows, POSIX (Linux and Mac OS X), and the web.

pub.dev

주말인지 알아서 그런지 정말 팍센 코드다. 오류를 도대체 몇 군데를 발견하고, 수정하고, 보고 타이핑하는데도 이렇게 힘들다니. 더군다나 작동을 잘 안한다.

main.dart

import 'package:flutter/material.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'addTodo.dart';
import 'clearList.dart';
import 'todo.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @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)
      },
    );
  }

  Future<Database> initDatabase() async {
    return openDatabase(
      join(await getDatabasesPath(), 'todo_database.db'),
      onCreate: (db, version) {
        return db.execute(
          "CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT,"
              "title TEXT, content TEXT, active INTEGGER)",
        );
      },
      version: 1,
    );
  }
}

class DatabaseApp extends StatefulWidget {
  final Future<Database> db;

  const DatabaseApp(this.db, {super.key});

  @override
  State<DatabaseApp> createState() => _DatabaseAppState();
}

class _DatabaseAppState extends State<DatabaseApp> {
  Future<List<Todo>>? todoList;

  @override
  void initState() {
    super.initState();
    todoList = getTodos();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text('Database Example'), actions: <Widget>[
          ElevatedButton(
              onPressed: () async {
                await Navigator.of(context).pushNamed('/clear');
                setState(() {
                  todoList = getTodos();
                });
              },
              child: const Text(
                '완료한 일',
                style: TextStyle(color: Colors.black),
              ))
        ]),
        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) {
                                  return AlertDialog(
                                    title: Text('${todo.id} : ${todo.title}'),
                                    content: const Text('Todo를 체크하시겠습니까?'),
                                    actions: <Widget>[
                                      TextButton(
                                          onPressed: () {
                                            setState(() {
                                              todo.active == 1
                                                  ? todo.active = 0 : todo
                                                  .active = 1;
                                            });
                                            Navigator.of(context).pop(todo);
                                          },
                                          child: const Text('예')),
                                      TextButton(
                                          onPressed: () {
                                            Navigator.of(context).pop(todo);
                                          },
                                          child: const 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: const Text('예')),
                                      TextButton(
                                          onPressed: () {
                                            Navigator.of(context).pop(todo);
                                          },
                                          child: const Text('아니요')),
                                    ],
                                  );
                                });
                            _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 {
                _allUpdate();
              },
              heroTag: null,
              child: const Icon(Icons.update),
            ),
          ],
        ));
  }

  void _allUpdate() async {
    final Database database = await widget.db;
    await database.rawUpdate('update todos set active = 1 where active = 0');
    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(), 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);
    setState(() {
      todoList = getTodos();
    });
  }

  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']);
    });
  }
}

 

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(){
    return {
      'id': id,
      'title': title,
      'content': content,
      'active': active,
    };
  }
}

clearList.dart

import 'package:flutter/material.dart';
import 'package:sqflite/sqlite_api.dart';
import 'package:doit10_1/todo.dart';

class ClearListApp extends StatefulWidget {
  Future<Database> database;

  ClearListApp(this.database, {super.key});

  @override
  State<StatefulWidget> createState() => _ClearListApp();
}

class _ClearListApp extends State<ClearListApp> {
  Future<List<Todo>>? clearList;

  @override
  void initState() {
    super.initState();
    clearList = getClearList();
  }

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('이미 한일'),
      ),
      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!),
                            Container(
                              height: 1,
                              color: Colors.blue,
                            )
                          ],
                        ),
                      );
                    },
                    itemCount: (snapshot.data as List<Todo>).length,
                  );
                }
            }
            return const Text('No data');
          },
          future: clearList,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          final result = await showDialog(
              context: context,
              builder: (BuildContext context) {
                return AlertDialog(
                  title: const Text('완료한 일 삭제'),
                  content: const Text('완료한 일을 모두 삭제할까요?'),
                  actions: <Widget>[
                    TextButton(
                        onPressed: () {
                          Navigator.of(context).pop(true);
                        },
                        child: const Text('예')),
                    TextButton(
                        onPressed: () {
                          Navigator.of(context).pop(false);
                        },
                        child: const Text('아니요')),
                  ],
                );
              });
          if (result == true) {
            _removeAllTodos();
          }
        },
        child: const Icon(Icons.remove),
      ),
    );
  }

  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 shere active=1');

    return List.generate(maps.length, (i) {
      return Todo(
          title: maps[i]['title'].toString(),
          content: maps[i]['content'].toString(),
          id: maps[i]['id']);
    });
  }

  void _removeAllTodos() async {
    final Database database = await widget.database;
    database.rawDelete('delete from todos wher active=1');
    setState(() {
      clearList = getClearList();
    });
  }
}

addTodo.dart

import 'package:flutter/material.dart';
import 'package:sqflite/sqlite_api.dart';
import 'todo.dart';

class AddTodoApp extends StatefulWidget {
  final Future<Database> db;

  const AddTodoApp(this.db, {super.key});

  @override
  State<AddTodoApp> createState() => _AddTodoAppState();
}

class _AddTodoAppState extends State<AddTodoApp> {
  TextEditingController? titleController;
  TextEditingController? contentController;

  @override
  void initState() {
    super.initState();
    titleController = TextEditingController();
    contentController = TextEditingController();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Todo 추가'),
      ),
      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: '할일'),
              ),
            ),
            ElevatedButton(
              onPressed: () {
                Todo todo = Todo(
                    title: titleController!.value.text,
                    content: contentController!.value.text,
                    active: 0);
                Navigator.of(context).pop(todo);
              },
              child: const Text('저장하기'),
            )
          ],
        ),
      ),
    );
  }
}

 

완료한 일에 아무것도 안 뜨는 문제가 발생한다. 체크에 true로 바뀌면 완료한 일로 보내져야 하는데, 그렇게 안된다.

그것말고도 작동 안되는 게 있는 듯하다.

어쨌든 오늘은 여기까지..

질문은 데이터베이스라 함은 외부 저장소인 줄 알았는데, 아닌가 보다. 내부 저장소도 데이터베이스라고 부르는건가?

 

그리고 자꾸 에뮬레이터에 자꾸 이런 오류 메세지가 뜬다.

System UI isn't responding

컴퓨터가 꼬지면 그렇단다.

안드로이드 스튜디오 끄고, 윈도우 내컴퓨터를 실행한 후, userdata-qemu를 삭제하면 해결된다. 다만, 이렇게 했을 경우 에뮬에 설정했던 모든 정보가 날아간다.

에뮬에 앱들이 자꾸 추가되니깐 문제가 되는 거 같다. 주기적으로 삭제를 하던지, 아니면 설치했던 앱들을 삭제해 보는 방법을 선택해 봐야겠다.

 

 

반응형