지난 주 바쁜 일정으로 인해 3주 차 프로젝트가 한 주 늦어지게 업데이트 되었네요 되도록 매 주 같은 시간에 업로드 하려고 하는데 쉽지 않네요…바빠서 업로드 못한거는 핑계고 앞으로 매 주 꾸준히 업로드 하도록 노력하겠습니다.

이분 주차 프로젝트에서는 flutter를 이용해 간단한 메모장 앱을 만들어 보겠습니다.이 앱에서는 메모를 작성하고, 저장하며, 목록에서 메모를 볼 수 있는 기능을 구현해 보겠습니다.

작성된 메모는 로컬에 저장하고, 앱이 재 시작될 때도 데이터를 유지하도록 해보겠습니다.

프로젝트 준비

  1. Flutter 프로젝트 생성
    • VScode를 열고, Command Palette(Ctrl+Shift+P)를 엽니다.
  2. ‘Flutter: New Project’를 선택하고 이름을 ‘notepad_app으로 입력 합니다.
  3. 프로젝트 생성할 디렉토리를 선택합니다.

프로젝트 구조

이번 메모장 앱은 다음과 같은 구조로 나누어 구현하겠습니다.

  1. main.dart: 앱의 시작점 입니다.
  2. note.dart: 메모 데이터를 정의하는 클래스
  3. note_list_page.dart: 메모 목록 화면을 구성하는 클래스
  4. note_edit_page.dart: 메모를 작성하거나 수정하는 화면을 구성하는클래스

각 파일을 별도로 작성하여 유지보수성을 높이고, 코드의 재사용성을 증가시킬 수 있습니다.

1단계: Note 클래스 작성

먼저, 메모 데이터를 정의하는 ‘Note’클래스를 작성합니다.

  1. lib 디렉토리 아래에 note.dart파일을 생성합니다.
class Note {
  String title;
  String content;

  Note({
    required this.title,
    required this.content,
  });

  // Note 인스턴스를 JSON 형식으로 변환합니다.
  Map<String, dynamic> toJson() => {
    'title': title,
    'content': content,
  };

  // JSON 데이터를 Note 인스턴스로 변환합니다.
  factory Note.fromJson(Map<String, dynamic> json) => Note(
    title: json['title'],
    content: json['content'],
  );
}

2단계: NoteEditPage 클래스 작성

메모를 작성하거나 수정하는 화면을 구성하는 ‘NoteEditPage’ 클래스를 작성합니다.

  1. lib 디렉토리 아래에 note_edit_page.dart 파일을 생성합니다.
  2. ‘NoteEditPage’ 클래스를 작성합니다.
//메모를 작성하거나 수정하는 화면을 구성하는 클래스
import 'package:flutter/material.dart';
import 'note.dart';

class NoteEditPage extends StatefulWidget {
  final Note? note; //기존 메모를 수정할 때 사용, 새 메모일 경우 null

  //생성자에서 Note 객체를 받아옵니다.
  NoteEditPage({this.note});

  //상태를 관리하는 State클래스를 생성합니다.
  @override
  _NoteEditPageState createState() => _NoteEditPageState();
}

//NoteEditPage의 상태를 관리하는 _NoteEditPage 클래스
class _NoteEditPageState extends State<NoteEditPage> {
  late TextEditingController _titleController; //제목 입력을 위한 TextEditController
  late TextEditingController _contentController; //내용 입력을 위한 TextEditController

  @override
  void initState() {
    super.initState();
    //초기화 시, 전달된 Note 객체를 사용하여 컨트롤러에 초기값을 설정합니다.
    _titleController = TextEditingController(
      text: widget.note?.title ?? '', //메모 제목이 있으면 그 값으로, 없으면 빈 문자열로 초기화 합니다.
    );
    _contentController = TextEditingController(
      text: widget.note?.content ?? '', //메모 내용이 있으며 그 값으로, 없으며 빈 문자열로 초기화 합니다.
    );
  }

  @override
  void dispose() {
    //화면이 종료될 때 컨트롤러를 해제합니다.
    _titleController.dispose();
    _contentController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        //제목을 지정합니다. 메모 수정 시 'Edit Not',새 메모 작성 시 'New note'가 표시됩니다.
        actions: [
          IconButton(
            icon: const Icon(Icons.save), //저장 버튼 아이콘을 표시합니다.
            onPressed: () {
              //저장 버튼이 눌리면 현재 입려된 제목과 내용을 Note 객체로 만들어서 반환합니다.
              final newNote = Note(
                title: _titleController.text, //입력된 제목을 가져옵니다.
                content: _contentController.text, //입력된 내용을 가져옵니다.
              );
              Navigator.pop(context, newNote); //현재 화면을 닫고, 작성된 메모를 반환합니다.
            },
          )
        ],
      ),
      body: Padding(
          padding: const EdgeInsets.all(16.0), // 전체 화면에 여백을 줍니다.
          child: Column(
            children: [
              TextField(
                controller: _titleController, //제목 입력을 위한 TextField
                decoration: const InputDecoration(
                    labelText: 'Title'), //'Title'레이블을 표시합니다.
              ),
              const SizedBox(height: 16.0), //제목과 내용 입력 사이에 간격을 추가
              TextField(
                controller: _contentController, //내용 입력을 위한 TextField
                decoration: const InputDecoration(labelText: 'Content'),
                maxLines: 10, //최대 10줄까지 입력할 수 있습니다.
              )
            ],
          )),
    );
  }
}

3단계: NoteListPage 클래스

이제 메모 목록 화면을 구성하는 ‘NoteListPage’클래스를 작성합니다. 이 화면에서는 저장된 메모들을 리스트 형식으로 표시하고, 새로운 메모를 작성할 수 있는 버튼을 만듭니다.

  1. lib 디렉토리에 note_list_page.dart파일을 생성합니다.
  2. ‘NoteListPage’ 클래스를 작성합니다.
import 'package:flutter/material.dart'; // Flutter의 기본 위젯을 사용하기 위해 import합니다.
import 'package:shared_preferences/shared_preferences.dart'; // 로컬 데이터 저장을 위해 import합니다.
import 'dart:convert'; // JSON 변환을 위해 import합니다.
import 'note.dart'; // Note 클래스를 사용하기 위해 import합니다.
import 'note_edit_page.dart'; // NoteEditPage 클래스를 사용하기 위해 import합니다.

// 메모 목록 화면을 구성하는 NoteListPage 클래스입니다.
class NoteListPage extends StatefulWidget {
  @override
  _NoteListPageState createState() => _NoteListPageState(); // 상태를 관리하는 State 클래스 생성합니다.
}

// NoteListPage의 상태를 관리하는 _NoteListPageState 클래스입니다.
class _NoteListPageState extends State<NoteListPage> {
  List<Note> notes = []; // 메모 목록을 저장하는 리스트입니다.

  @override
  void initState() {
    super.initState();
    _loadNotes(); // 앱이 시작될 때 저장된 메모를 불러옵니다.
  }

  // SharedPreferences에서 메모 데이터를 불러오는 메서드입니다.
  void _loadNotes() async {
    final prefs = await SharedPreferences.getInstance(); // SharedPreferences 인스턴스를 가져옵니다.
    final String? notesString = prefs.getString('notes'); // 저장된 메모 데이터를 불러옵니다.
    if (notesString != null) {
      // 불러온 메모 데이터를 JSON 리스트에서 Note 객체 리스트로 변환합니다.
      setState(() {
        notes = List<Note>.from(
          json.decode(notesString).map((data) => Note.fromJson(data))
        );
      });
    }
  }

  // 메모 데이터를 SharedPreferences에 저장하는 메서드입니다.
  void _saveNotes() async {
    final prefs = await SharedPreferences.getInstance(); // SharedPreferences 인스턴스를 가져옵니다.
    final String notesString = json.encode(notes.map((note) => note.toJson()).toList()); // 메모 리스트를 JSON으로 변환합니다.
    await prefs.setString('notes', notesString); // 변환된 JSON 데이터를 저장합니다.
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Notes'), // AppBar의 제목을 설정합니다.
      ),
      body: notes.isEmpty
          ? Center(
              child: Text(
                'No notes available', // 메모가 없을 경우 표시되는 메시지입니다.
                style: TextStyle(fontSize: 18), // 메시지의 스타일을 설정합니다.
              ),
            )
          : ListView.builder(
              itemCount: notes.length, // 메모 개수에 따라 리스트 아이템 수를 결정합니다.
              itemBuilder: (context, index) {
                return ListTile(
                  title: Text(notes[index].title), // 메모의 제목을 리스트 아이템에 표시합니다.
                  onTap: () async {
                    final editedNote = await Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => NoteEditPage(note: notes[index]), // 메모 수정 화면으로 이동합니다.
                      ),
                    );
                    if (editedNote != null) {
                      setState(() {
                        notes[index] = editedNote; // 수정된 메모를 리스트에 반영합니다.
                        _saveNotes(); // 메모 수정 후 저장합니다.
                      });
                    }
                  },
                  onLongPress: () async {
                    // 메모를 길게 누르면 삭제 확인 대화상자를 표시합니다.
                    bool? shouldDelete = await showDialog(
                      context: context,
                      builder: (context) {
                        return AlertDialog(
                          title: Text('Delete Note'), // 대화상자의 제목을 설정합니다.
                          content: Text('Are you sure you want to delete this note?'), // 대화상자의 내용을 설정합니다.
                          actions: [
                            TextButton(
                              child: Text('Cancel'), // 취소 버튼을 추가합니다.
                              onPressed: () => Navigator.of(context).pop(false), // 취소 시 false 반환
                            ),
                            TextButton(
                              child: Text('Delete'), // 삭제 버튼을 추가합니다.
                              onPressed: () => Navigator.of(context).pop(true), // 삭제 확인 시 true 반환
                            ),
                          ],
                        );
                      },
                    );

                    if (shouldDelete == true) {
                      setState(() {
                        notes.removeAt(index); // 메모를 리스트에서 삭제합니다.
                        _saveNotes(); // 메모 삭제 후 저장합니다.
                      });

                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text("Note deleted")), // 메모가 삭제되었음을 알리는 메시지 표시
                      );
                    }
                  },
                );
              },
            ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add), // 추가 버튼 아이콘을 표시합니다.
        onPressed: () async {
          final newNote = await Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => NoteEditPage(), // 새 메모 작성 화면으로 이동합니다.
            ),
          );
          if (newNote != null) {
            setState(() {
              notes.add(newNote); // 새로 작성된 메모를 리스트에 추가합니다.
              _saveNotes(); // 새 메모 추가 후 저장합니다.
            });
          }
        },
      ),
    );
  }
}

4단계: main.dart작성

이제 ‘main.dart’ 파일을 작성하여, 앞서 작성한 ‘NoteListPage를 앱의 메인 화면으로 설정합니다.

main.dart파일을 열고 다음 코드를 작성합니다.

import 'package:flutter/material.dart'; // Flutter의 기본 위젯을 사용하기 위해 import합니다.
import 'note_list_page.dart'; // NoteListPage 클래스를 사용하기 위해 import합니다.

void main() {
  runApp(MyApp()); // 앱의 진입점, MyApp 위젯을 실행합니다.
}

// MyApp 클래스 정의, 앱의 전반적인 설정을 관리합니다.
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Notepad App', // 앱의 제목을 설정합니다.
      theme: ThemeData(
        primarySwatch: Colors.blue, // 앱의 기본 테마 색상을 설정합니다.
        visualDensity: VisualDensity.adaptivePlatformDensity, // 플랫폼 밀도에 따라 UI 요소 크기를 자동 조정합니다.
      ),
      debugShowCheckedModeBanner: false, // 디버그 모드에서 DEBUG 라벨을 제거합니다.
      home: NoteListPage(), // 앱의 메인 화면으로 NoteListPage를 설정합니다.
    );
  }
}

5단계: 앱 실행

모든 코드를 작성한 후 , 앱을 실행하여 정상적으로 작동하는지 확인합니다.

이번 프로젝트를 처음부터 잘 따라오셨다면 다음과 같은 화면이 표시 되어야 합니다.

6단계: 오류 대처

만약 앱을 실행하면 아래와 같은 오류가 발생할 수 있습니다.

  1. ‘shared_preferences’ 패키지설치
    • ‘pubspec.yaml’파일을 열고 ‘shared_preferences’ 패키지를 추가합니다.

shared_preferences는 Flutter에서 간단한 데이터를 로컬 스토리지에 저장하고 관리하고 저장할 수 있는 패키지입니다. 이 패키지는 주로 사용자 설정, 앱 상태, 간단한 데이터 등을 저장할 때 사용됩니다. 기본적으로 키-값 싸을 저장하는 방식으로 작동되며, 데이터를 쉽게 저장하고 불러올 수 있습니다.

지금까지 Flutter를 이용하여 간단한 메모장 앱을 만들어 보았습니다. 각 클래스를 단계적으로 작성하면서 코드가 어떻게 서로 연결되는지 이해하고, 이를 통해 더 복잡한 앱을 개발할 수 있는 기초가 되었으면 합니다.

이번 프로젝트에서는 Flutter의 상태 관리, 리스트 처리, 그리고 기본적인 데이터 저장 및 불러오기 기능을 다뤄봤습니다. 질문이 있거나 추가적인 도움이 필요하다면 언제든지 댓글로 남겨주세요!


TechTinkerer's에서 더 알아보기

구독을 신청하면 최신 게시물을 이메일로 받아볼 수 있습니다.

댓글 남기기

  • Understanding Pointers and Memory Management in C++

    [Tutorial] · 2026-04-30 05:10 UTC Understanding Pointers and Memory Management in C++ 💡 TL;DR Mastering pointers in C++ is crucial for efficient memory management and writing effective code. 📚 Learning Objectives This tutorial covers the fundamentals of pointers in C++, including declaration, initialization, and memory management. Students will learn how to effectively use pointers to…

  • Building a Command-Line Calculator with C++

    [Tutorial] · 2026-04-30 04:08 UTC Building a Command-Line Calculator with C++ 💡 TL;DR Learn how to build a command-line calculator in C++ that takes user input and performs basic arithmetic operations. 📚 Learning Objectives This tutorial guides you through creating a basic command-line calculator in C++. You’ll learn how to take user input, perform arithmetic…

  • Mastering Python Data Structures for Efficient Coding

    [Tutorial] · 2026-04-30 03:05 UTC Mastering Python Data Structures for Efficient Coding 💡 TL;DR Learn about Python’s fundamental data structures – arrays, lists, tuples, and dictionaries – to write efficient and scalable code. 📚 Learning Objectives This tutorial covers the essential Python data structures – arrays, lists, tuples, and dictionaries. You’ll learn about their usage,…

  • Introduction to Object-Oriented Programming in Python

    [Tutorial] · 2026-04-30 02:02 UTC Introduction to Object-Oriented Programming in Python 💡 TL;DR Learn the fundamentals of object-oriented programming in Python, including classes and objects, inheritance, and polymorphism. 📚 Learning Objectives This tutorial introduces the basics of object-oriented programming in Python, covering classes, objects, inheritance, and polymorphism. By the end of this tutorial, beginners will…

  • Complete Guide to Python List Comprehensions

    [Tutorial] · 2026-04-30 01:00 UTC Complete Guide to Python List Comprehensions 💡 TL;DR Master Python list comprehensions to write concise and efficient code for data manipulation and transformation tasks. 📚 Learning Objectives This tutorial covers the basics of Python list comprehensions, including syntax, use cases, and execution results. You’ll learn how to write efficient and…

← 뒤로

응답해 주셔서 감사합니다. ✨

TechTinkerer's에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기

TechTinkerer's에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기