지난 주 바쁜 일정으로 인해 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에서 더 알아보기

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

댓글 남기기

  • The Fate of Greenland hangs in the Balance: A Global Power Play

    The fate of Greenland has become a focal point of global politics, with multiple nations vying for control over the strategic island. In recent weeks, several countries have expressed interest in acquiring or partnering with Greenland to further their interests. This article will examine the situation and explore the potential implications for international relations. The…

  • **South Korea’s Ambassador to the US Returns from Absence**

    After a year-long vacancy, South Korean Ambassador to the United States Kevin Kim has returned to his post. The ambassador’s return comes as a welcome relief for the South Korean government, which had been without an ambassador to the US since Kim’s departure in 2022. The cause of Kim’s absence was not publicly disclosed, but…

  • Building a Dynamic Landing Page: Mastering HTML, CSS, & JavaScript

    Welcome to My Website This is a basic landing page example. Click Me!

  • Mastering File Input/Output in C++

    [Tutorial] · 2026-01-15 08:44 UTC Mastering File Input/Output in C++ 💡 TL;DR Learn how to read from and write to files in C++ using the ifstream and ofstream objects, along with clear explanations of key concepts. 📚 Learning Objectives This tutorial explores file input/output operations in C++, covering essential concepts, practical examples, and best practices…

  • Mastering the Fundamentals of Object-Oriented Programming in C++

    [Tutorial] · 2026-01-15 04:15 UTC Mastering the Fundamentals of Object-Oriented Programming in C++ 💡 TL;DR Learn how to structure your code with classes, define objects, encapsulate data, and leverage inheritance for efficient development. 📚 Learning Objectives This tutorial introduces object-oriented programming concepts in C++, focusing on classes, objects, encapsulation, and inheritance. You’ll gain practical skills…

← 뒤로

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

TechTinkerer's에서 더 알아보기

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

계속 읽기

TechTinkerer's에서 더 알아보기

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

계속 읽기