지난 주 바쁜 일정으로 인해 3주 차 프로젝트가 한 주 늦어지게 업데이트 되었네요 되도록 매 주 같은 시간에 업로드 하려고 하는데 쉽지 않네요…바빠서 업로드 못한거는 핑계고 앞으로 매 주 꾸준히 업로드 하도록 노력하겠습니다.
이분 주차 프로젝트에서는 flutter를 이용해 간단한 메모장 앱을 만들어 보겠습니다.이 앱에서는 메모를 작성하고, 저장하며, 목록에서 메모를 볼 수 있는 기능을 구현해 보겠습니다.
작성된 메모는 로컬에 저장하고, 앱이 재 시작될 때도 데이터를 유지하도록 해보겠습니다.
프로젝트 준비
- Flutter 프로젝트 생성
- VScode를 열고, Command Palette(Ctrl+Shift+P)를 엽니다.
- ‘Flutter: New Project’를 선택하고 이름을 ‘notepad_app으로 입력 합니다.
- 프로젝트 생성할 디렉토리를 선택합니다.
프로젝트 구조
이번 메모장 앱은 다음과 같은 구조로 나누어 구현하겠습니다.
- main.dart: 앱의 시작점 입니다.
- note.dart: 메모 데이터를 정의하는 클래스
- note_list_page.dart: 메모 목록 화면을 구성하는 클래스
- note_edit_page.dart: 메모를 작성하거나 수정하는 화면을 구성하는클래스
각 파일을 별도로 작성하여 유지보수성을 높이고, 코드의 재사용성을 증가시킬 수 있습니다.
1단계: Note 클래스 작성
먼저, 메모 데이터를 정의하는 ‘Note’클래스를 작성합니다.
- 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’ 클래스를 작성합니다.
- lib 디렉토리 아래에 note_edit_page.dart 파일을 생성합니다.
- ‘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’클래스를 작성합니다. 이 화면에서는 저장된 메모들을 리스트 형식으로 표시하고, 새로운 메모를 작성할 수 있는 버튼을 만듭니다.
- lib 디렉토리에 note_list_page.dart파일을 생성합니다.
- ‘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단계: 오류 대처
만약 앱을 실행하면 아래와 같은 오류가 발생할 수 있습니다.


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

shared_preferences는 Flutter에서 간단한 데이터를 로컬 스토리지에 저장하고 관리하고 저장할 수 있는 패키지입니다. 이 패키지는 주로 사용자 설정, 앱 상태, 간단한 데이터 등을 저장할 때 사용됩니다. 기본적으로 키-값 싸을 저장하는 방식으로 작동되며, 데이터를 쉽게 저장하고 불러올 수 있습니다.
지금까지 Flutter를 이용하여 간단한 메모장 앱을 만들어 보았습니다. 각 클래스를 단계적으로 작성하면서 코드가 어떻게 서로 연결되는지 이해하고, 이를 통해 더 복잡한 앱을 개발할 수 있는 기초가 되었으면 합니다.
이번 프로젝트에서는 Flutter의 상태 관리, 리스트 처리, 그리고 기본적인 데이터 저장 및 불러오기 기능을 다뤄봤습니다. 질문이 있거나 추가적인 도움이 필요하다면 언제든지 댓글로 남겨주세요!
댓글 남기기