새벽의 기록

[Toy Project] Flutter로 만드는 To Do List App #1 본문

[Flutter]/[Toy Project]

[Toy Project] Flutter로 만드는 To Do List App #1

OneTen 2024. 4. 10. 10:00

Intro

 

이번 프로젝트는 GitHub Actions를 이용한 CI/CD 까지 해보기 위해서
일단 간단한 To Do List App을 만들고 순차적으로 기능을 추가하며 개발해보려고 한다.

 

앱 자체 개발이 메인이 아니라 앱을 개발하는 과정과
그 외의 신경 써야 하는 것들을 좀 더 공부하기 위한 프로젝트라고 할 수 있겠다.


App 코드들과 실행화면

 

간단하게 만든 초기 버전 To Do List App

 

 

 

아래 사진은 파일 구조입니다.

 

pubspec.yaml

dependencies:
  flutter_slidable: <latest_version>
  hive: ^2.2.3
  hive_flutter: ^1.1.0

dependencies에 위의 코드들을 추가해야 앱이 정상적으로 실행된다.

 

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_todo_app/home_page.dart';
import 'package:hive_flutter/hive_flutter.dart';

void main() async {
  //init the hive
  await Hive.initFlutter();

  //open the box
  var box = await Hive.openBox('mybox');

  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomePage(),
      //HomePage(),
    );
  }
}

 

home_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_todo_app/data/database.dart';
import 'package:flutter_todo_app/util/dialog_box.dart';
import 'package:flutter_todo_app/util/todo_tile.dart';
import 'package:hive_flutter/hive_flutter.dart';

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  // reference the hive box
  final _myBox = Hive.box('mybox');
  ToDoDatabase db = ToDoDatabase();

  @override
  void initState() {
    if (_myBox.get("TODOLIST") != null) {
      db.getDatabaseData();
    } else {
      db.createInitialData();
    }

    super.initState();
  }

  // text controller
  final _controller = TextEditingController();

  // checkbox was tapped
  void checkBoxChanged(bool? value, int index) {
    setState(() {
      db.toDoList[index][1] = !db.toDoList[index][1];
    });
    db.updateDatabase();
  }

  // create a new task
  void createNewTask() {
    showDialog(
      context: context,
      builder: (context) {
        return DialogBox(
          controller: _controller,
          onCancel: () => Navigator.of(context).pop(),
          onSave: saveNewTask,
        );
      },
    );
  }

  // save the new task
  void saveNewTask() {
    setState(() {
      db.toDoList.add([_controller.text, false]);
      _controller.clear();
      Navigator.of(context).pop();
      db.updateDatabase();
    });
  }

  // delete a task
  void deleteTask(int index) {
    setState(() {
      db.toDoList.removeAt(index);
    });
    db.updateDatabase();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.yellow[200],
      appBar: AppBar(
        backgroundColor: Colors.yellow,
        title: Text('To Do List'),
        centerTitle: true,
        elevation: 0,
      ),
      body: ListView.builder(
        itemCount: db.toDoList.length,
        itemBuilder: (context, index) {
          return TodoTile(
            taskName: db.toDoList[index][0],
            taskCompleted: db.toDoList[index][1],
            onChanged: (value) => checkBoxChanged(value, index),
            deleteFunction: (context) => deleteTask(index),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.yellow,
        onPressed: createNewTask,
        child: Icon(Icons.add),
      ),
    );
  }
}

 

lib/data/database.dart


import 'package:hive_flutter/hive_flutter.dart';

class ToDoDatabase{

  List toDoList = [];

  //reference our box
  final _myBox = Hive.box('mybox');

  // run this function when the app starts
  void createInitialData() {
    toDoList = [
      ["Make Tutorial", false],
      ["Do Exercise", false],
    ];
  }

  // get the data from the database
  void getDatabaseData() {
    toDoList = _myBox.get("TODOLIST");
  }

  // update the database
  void updateDatabase() {
    _myBox.put("TODOLIST", toDoList);
  }


}

 

lib/util/dialog_box.dart

import 'package:flutter/material.dart';
import 'package:flutter_todo_app/util/my_button.dart';

class DialogBox extends StatelessWidget {
  final controller;
  VoidCallback onSave;
  VoidCallback onCancel;

  DialogBox({
    super.key,
    required this.controller,
    required this.onCancel,
    required this.onSave,
  });

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      backgroundColor: Colors.yellow.shade300,
      content: Container(
        height: 120,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            // get user input
            TextField(
              controller: controller,
              decoration: InputDecoration(
                enabledBorder: OutlineInputBorder(
                  borderSide: BorderSide(
                    color: Colors.yellow.shade800,
                  ),
                ),
                hintText: "Add a new task",
              ),
            ),

            // buttons -> save+ cancel
            Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                // save button
                MyButton(text: "Save", onPressed: onSave),

                SizedBox(
                  width: 8,
                ),

                // cancel button
                MyButton(text: "Cancel", onPressed: onCancel)
              ],
            ),
          ],
        ),
      ),
    );
  }
}

 

lib/util/my_button.dart

import 'package:flutter/material.dart';

class MyButton extends StatelessWidget {
  final String text;
  VoidCallback onPressed;

  MyButton({
    super.key,
    required this.text,
    required this.onPressed,
  });

  @override
  Widget build(BuildContext context) {
    return MaterialButton(
      onPressed: onPressed,
      color: Colors.yellow,
      child: Text(text),
    );
  }
}

 

lib/util/todo_tile.dart

import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';

class TodoTile extends StatelessWidget {
  final String taskName;
  final bool taskCompleted;
  Function(bool?)? onChanged;
  Function(BuildContext)? deleteFunction;

  TodoTile({
    super.key,
    required this.taskName,
    required this.taskCompleted,
    required this.onChanged,
    required this.deleteFunction,
  });

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(left: 25, right: 25, top: 25),
      child: Slidable(
        endActionPane: ActionPane(

          // Pane size
          extentRatio: 0.3,

          motion: ScrollMotion(),
          children: [
            SlidableAction(
              onPressed: deleteFunction,
              icon: Icons.delete,
              backgroundColor: Colors.red.shade300,
              borderRadius: BorderRadius.circular(12),
            ),
          ],
        ),
        child: Container(
          padding: EdgeInsets.all(12),
          decoration: BoxDecoration(
            color: Colors.yellow,
            borderRadius: BorderRadius.circular(12),
          ),
          child: Row(
            children: [
              // checkbox
              Checkbox(
                value: taskCompleted,
                onChanged: onChanged,
                activeColor: Colors.black,
              ),

              // task name
              Text(
                taskName,
                style: TextStyle(
                  // strike through if task is completed
                  decoration: taskCompleted ? TextDecoration.lineThrough : null,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

 

 

Plus +@

새롭게 알았거나 기록해두고 싶은 잡다한 것들

 

Slidable 패키지 사용

이번에 새롭게 안 패키지라서 따로 정리해두었다.

https://dawning-record.tistory.com/64

 

[Flutter] flutter_slidable 패키지

유튜브 재생목록에서 영상을 지울 때 옆으로 밀어서 삭제하는 이펙트와 비슷한 패키지. 플러터에서 위젯을 밀어서 어떠한 이벤트가 나타나게 해주는 flutter_slidable 패키지. 아이폰에서는 자주 쓰

dawning-record.tistory.com

 

 

Hive 

이번 프로젝트에서 사용한 로컬 database인데 지금까지 sqflite만 써 봐서 생소했다.

Hive 는 가볍고 빠른 No SQL 기반 Database 를 제공하며, key-value 기반의 Database 로 꽤나 간편하다.

경우에 따라 다른 형태의 Database 를 사용하겠지만 아주 간단한 모바일 앱에서 사용하기에는 무리없이 사용 할 수 있어 보인다.

 

 

 

 

Comments