
Ⅰ. 시작하며
📌 MVVM 구조에 적응
📌 Form 유효성 검사와 텍스트 컨트롤러
Ⅱ. 본론
📌 1. 전체 폴더 구조 및 기초 뼈대 작업
📌 2. HomePage 구성 – 리스트와 앱바, ListView 기본 UI
📌 3. etailPage – 아이콘 재사용과 텍스트 본문 구성
📌 4. WritePage – TextFormField와 Form 구조 잡기(GitHub link)
Ⅲ. 정리하며
📌 TextFormField와 키보드 UX 고려
📌 구조화된 설계의 중요성
✍ 시작하며
MVVM 구조에 적응하는 데 시간이 걸렸다
블로그 앱을 만들기 전,
MVVM 아키텍처와
폴더 구조를 나누는 데
많은 고민을 했습니다.
특히
View와 ViewModel을
어떤 기준으로 나눌지
처음에는 막막했는데,
구조를 세워두고 나니
각 페이지 별로 역할이 명확해져
코드 유지보수에도 큰 도움이 되었어요.
Form 유효성 검사와 텍스트 컨트롤러를 처음부터 통합하자
글쓰기 화면을 만들면서
TextEditingController와
Form 유효성 검사를
한꺼번에 관리하려다
중간에 구조를 바꾸는
시행착오를 겪었어요.
그래서 처음부터
controller + validator + formKey를
체계적으로 설계하는 게
중요하다고 느꼈습니다.
🧱 본론
전체 폴더 구조 및 기초 뼈대 작업
처음부터
data, ui, model, repository 구조로 폴더를 나누고,
각 페이지(Home, Write, Detail)마다
각각의 ViewModel과 Widget 폴더를 생성했습니다.
구조가 명확하니까
나중에 ViewModel을 붙이거나
상태 관리를 추가할 때도
훨씬 수월할 거라고 생각했어요.
특히 재사용성을 고려해서
앱바와 리스트 아이템은
별도 위젯으로 추출했습니다.
main.dart에 ProviderScope로
감싸는 것도 잊지 않고 적용해줍니다.
lib/
├── data/
│ ├── model/
│ └── repository/
├── ui/
│ ├── pages/
│ │ ├── home/
│ │ │ ├── widgets/
│ │ │ ├── home_page.dart
│ │ │ └── home_view_model.dart
│ │ ├── detail/
│ │ │ ├── widgets/
│ │ │ ├── detail_page.dart
│ │ │ └── detail_view_model.dart
│ │ ├── write/
│ │ │ ├── widgets/
│ │ │ ├── write_page.dart
│ │ │ └── write_view_model.dart
│ └── widgets/
└── main.dart
HomePage 구성 – 리스트와 앱바, ListView 기본 UI
홈 화면은
단순한 리스트로 구현할 수도 있었지만,
이미지가 오른쪽에 뜨고
왼쪽은 흰 박스로
텍스트가 표시되는 UI를 위해
Stack과 Positioned를 활용했어요.
AspectRatio, ClipRRect 등도 사용하여
디자인 완성도를 높였고,
리스트 아이템을 ListView.separated로 구성해
간격도 깔끔하게 맞췄습니다.
- 앱바 제목을 BLOG로 지정하고, 테마에서 공통 폰트를 지정했습니다.
- 리스트는 ListView.separated + Stack을 활용해 이미지와 텍스트를 겹치게 구성했고,
좌우 레이아웃 분리를 위해 Positioned, AspectRatio, ClipRRect까지 함께 활용했습니다. - 리스트 항목 클릭 시 DetailPage로 이동하는 네비게이션도 적용!
appBar: AppBar(title: Text('BLOG')),
HomeListView() // Stack 레이아웃 + Positioned 활용
DetailPage – 아이콘 재사용과 텍스트 본문 구성
상세 페이지에서는
상단에 삭제 및 수정 아이콘을 배치하고,
하단에는 이미지와 작성자,
날짜, 본문 콘텐츠를 보여주도록 구성했어요.
특히 아이콘을
반복적으로 사용하기 때문에
GestureDetector와
IconData를 받아서 처리하는
button 함수를
재사용 컴포넌트로 만들어
중복을 줄였어요.
- AppBar 우측에 삭제, 수정 버튼을 button() 함수로 재사용 가능하게 분리
- ListView로 전체 스크롤 가능하게 하고, 이미지 → 본문 → 작성자 → 날짜 순으로 배치
- TextOverflow.ellipsis, SizedBox 등을 사용해 레이아웃 정렬을 다듬었습니다.
appBar: AppBar(actions: [button(Icons.delete), button(Icons.edit)])
body: ListView(children: [...])
WritePage – TextFormField와 Form 구조 잡기
글쓰기 페이지는
유효성 검사(Form)를 포함해야 해서
StatefulWidget으로 만들고,
TextEditingController를
필드별로 선언했어요.
validator 함수와
formKey를 통해 유효성 검사를 진행하고,
완료 버튼에서 이를 체크하도록 구현했어요.
실제로 빈 값 입력 시
사용자에게 안내 메시지를 보여주도록 했고,
키보드 UX를 고려해
GestureDetector를 활용해
외부 터치 시 포커스가 해제되도록 구성했어요.
- TextEditingController는 총 3개(writer, title, content)로 분리
- Form + GlobalKey<FormState> 구조로 유효성 검사를 통합하고
formKey.currentState!.validate()로 완료 버튼을 구현했습니다. - 특히 expands, maxLines: null을 통해 콘텐츠 입력 영역의 레이아웃을 다듬었습니다.
TextFormField(
controller: titleController,
validator: (value) => value!.isEmpty ? '제목을 입력해 주세요' : null,
)
예시 파일은
아래 링크에서 확인할 수 있습니다.
https://github.com/Linayoo01/-250421-flutter_firebase_blog_app-main
GitHub - Linayoo01/-250421-flutter_firebase_blog_app-main
Contribute to Linayoo01/-250421-flutter_firebase_blog_app-main development by creating an account on GitHub.
github.com
🪄 정리하며
TextFormField와 키보드 UX 고려하기
이번에 느낀 건
Flutter 앱은
단순히 UI를 배치하는 게 아니라
작은 디테일이
전체 UX를 좌우한다는 점이에요.
단순한 버튼 하나도
터치 반경, 색상, 정렬 등
여러 요소를 고려해야 하고,
Form 처리도 생각보다 구조가 많았어요.
작은 요소 하나하나에
의미를 부여하며
작업하는 것이
정말 중요하다고 느꼈습니다.

갑자기 창피해지는
지난 나의 작업들..
- Scaffold를 GestureDetector로 감싸 빈 공간을 터치했을 때 키보드가 사라지도록 UX를 개선했습니다.
- TextInputAction, maxLines, expands 속성을 활용하면서 폼 사용성이 훨씬 좋아졌습니다.
구조화된 설계의 중요성
UI 구조와 입력 폼까지 완료되었으니,
이제 Firestore에 글을 저장하고
불러오는 기능을 구현할 예정이에요.
특히 이미지 업로드는
Firebase Storage를 통해 처리해야 하므로,
image_picker와 권한 설정까지 신경 써야 해요.
MVVM 구조를 유지하면서
Repository를
어떻게 구성할지도 고민하고 있어요.
다음 파트에서는
Firebase와 본격적으로 연결하며
데이터 저장과 불러오기 로직까지
마무리해 보려고 해요!
- 페이지를 나눌 때 AppBar의 차이만 있다면 페이지를 재활용할 수 있도록 설계했습니다.
- write_page.dart는 수정, 작성 페이지 모두에 사용할 수 있는 기본 구조로 개발해
확장성과 재사용성이 매우 뛰어났습니다.