최종프로젝트 -한 뼘

주혜림·2026년 3월 3일

최종프로젝트를 팀 단위로 작업하지 않고 개인작업을 하게 되었지만 할 수 있는 부분까지는 하고 싶어서 두렵지만 도전하게 되었다.

목표: 기능은 적더라도 완성도가 높은 앱 만들기

최소한의 MVP기능
1. 로그인 (Auth)-구글, 카카오
2. 데이터 읽기/쓰기/수정/삭제 (CRUD)
3. 외부 API활용 (지도, 날씨 등)
클린아키텍쳐를 적용
클린아키텍쳐 참조 링크
최종 실전프로젝트 가이드 노션 링크

📱 프로젝트: 한 뼘 (Han-ppyeom)

"작은 성취가 모여 만드는 큰 변화, 마음 웰빙 및 루틴 관리 서비스"

1. 프로젝트 개요 (소개)

배경: 거창한 목표보다 매일의 작은 성공(성취)을 통해 자존감을 회복하고 심리적 안정을 돕는 앱입니다.

대상: 일상의 무력감을 느끼거나 규칙적인 생활 습관(루틴) 형성이 필요한 사용자.

기간: 2026.03 ~ 현재 (진행 중)

주요 기술 사용 계획: Flutter(플러터), Riverpod(리버팟), Firebase(파이어베이스), GoRouter(고라우터)


1. 기획 아이디어

기획 가이드라인 참조 링크

1) 아이디어 서칭

인텔리픽 채용공고 중 인티그레이션은 한의학계 의료사업자를 위한
'웹 한의사 커뮤니티' / 한의원에서 필요한 약재나 기구 등 판매하는 '메디마켓 앱' / 한약 판매사이트 '웹 브랜드 수' / 한약 다이어트. 한의원에서 다이어트를 진행하며 기록할 수 있는 환자(소비자)를 위한 '린다이어트 앱' / 약침 브랜드 홍보 웹 / 개원, 경영 컨설팅 / 병원운영 SaaS / 치과의사 종합 플랫폼 '모어덴 웹' / 치위생사 전용 커뮤니티 '치즈톡 앱' / 치과위생사 구인구직 플랫폼 '치크루팅 앱'
한의계 플랫폼 메디스트림을 운영하고 있다.

주요업무

  • 모바일 하이브리드 앱 개발 및 운영
  • 플러터 기반 모바일 앱 서비스 구축
  • 플러터 라이브러리 개발 및 유지보수

기술 스택

  • flutter
  • Vue.js
  • React
  • ios

자격 요건

  • 다트 프로그래밍
  • 플러터 개발 전반에 대한 이해
  • 플러터 상용서비스 출시 및 운영 경험 있어야 함
  • GetX, Provider 등 상태관리 패턴 사용경험 있어야 함
  • 영문 개발 문서 독해력

우대사항 참조

채용공고 링크

[ 목표 ]

해당 기업에서 운영중인 다이어트 앱과 관련하여 사용자에게 원하는 목표까지 앱을 사용하며 어떤 경험을 제공하고, 기능을 사용했는지, 보완할 부분은 어떤 것이 있는지 분석해보자.
필수적인 CRUD기능을 먼저 구현하고 추후 로그인하고 연동하는 기능도 최대한 구현해보고 싶다.


앱 분석

🥦 린 다이어트

  • 혼자 다이어트를 하고 기록하는 것이 아니라 해당 지역의 한의원과 연동
  • 진료를 받고 처방 받은 한약 / 식단 / 운동을 병행
  • 다이어트 목표를 세우고 기록하고, 다이어트 관련 다양한 정보들을 얻으며 사용자 맞춤 관리
  • 관련 식품이나 한약재 등 부수입 광고

앱 실행
1- 로딩 중 광고화면이 나옴
2- 카카오로 로그인할지 동의하기 페이지
3- [로그인페이지]
'시작하기'-상단에 표시바가 있어서 진행정도를 알 수 있음
앱 바에 상담원 연결로 카톡과 연동되어서 채팅 가능
-> 이름 입력. 입력을 하지 않으면 빨갛게 테두리가 생기며 하단 '내용을 입력해주세요' 안내메시지
-> 이름칸 위에 주민등록번호 칸 생성. 빨간테두리 안내메시지 '입력한 정보를 다시 확인해주세요.'
-> 휴대폰번호 입력. 주민번호 입력완료 시 바로 통신사 체크하는 팝업 뜸.
통신사 체크 후 상단에 휴대폰번호 칸 생성. 빨간테두리 안내메시지 '최소 11자 이상 입력해주세요.'
-> 다음 버튼 클릭
-> 본인인증 약관 동의(위치기반 서비스 이용약관(선택), 마케팅 수신동의(선택) 등)
-> 동의하면 인증번호 입력칸 상단 생성. 인증번호가 발송되었다는 팝업! 인증번호 숫자 입력 카운트. 문자로 온 팝업을 인증하면 인증완료되었습니다 팝업메시지
-> 환영메시지 페이지 다음버튼
-> 위치권한 허용하기 페이지 다음버튼. 애니메이션 영상.
엑세스 허용 관련 팝업 메시지
-> 근처 린다이어트 한의원 찾는 로딩이미지
-> 2가지 [선택 페이지] of [나중에 설정 가능] 선택
근처 한의원 위치정보를 받고 연결 완료. 진료후 밀착관리받기 버튼.
(페이지 내용 1) 가까운 한의원과 원장님 정보
2) 린다이어트 이용후기 (이용후기 더보기 펼치는 기능)
3) 제공 혜택 정보
4) 진료 후 관리받기 권유 및 유도 버튼)
-> [진료신청서 페이지] 2가지 [비대면] or [대면] 선택
진료날짜 선택 팝업. 진료 시간 선택 다음 팝업(오전/오후).
진료 동의 팝업. 진료신청완료 애니메이션과 함께 페이지 전환
-> 진료신청내역 페이지
-> [문진표 작성 페이지] 목표와 경험(키, 몸무게(숫자입력), 최소 최대체중, 1년간 변화(버튼선택), 가족과체중 여부, 경험한 다이어트<버튼누르면 아래러 다른 선택지 추가됨. 힘들었던 점. 최근 다이어트 시점, >, 목표체중, 선호하는 방식, 체중계보유여부)
-> 생활습관. (직업, 근무시간, 운동빈도, 하는운동, 커피섭취량, 물섭취량, 음주빈도, 식사3끼 먹는시간, 기타 식사습관, 자주먹는 음식, 수면 시간, 수면상황과 질, 그 외 영향주는 생활습관)
선택지를 선택하지 않고 다음버튼을 누르면 해당 항목의 라인에 빨간테투리와 선택지를 입력해달라는 안내문구 팝업이 뜸.
-> 마지막 체질정보. (한달 내 복용한 건강기능식품/영양제, 한달 내 복용한 약, (있다면)약 복용 시작일, 약 복용빈도 일회성,일,주,월, 질환진단 경험, 진단명, 완치여부, 5년 내 수술경험, 대장내시경 경험, 출산경험, 임신 수유여부, 생리주기, 생리 후 부종, 평소 소화상태, 대변빈도, 수족냉증 여부, 땀의 양, 그 외 건강 참고사항)
-> 문진표작성 완료 애니메이션과 함께 페이지 전환
->[진료신청 내역 페이지] 다시 돌아옴 (하단에 문진단계 진행도)
의료정보연동 버튼
-> [의료정보연동 페이지] 동의 버튼
-> 정보연동 간편인증 수단 선택 페이지. 약관에 모두 동의 팝업
-> 네이버인증서 선택. 네이버로 이동해서 인증서 인증 완료. 다시 돌아와서 인증완료 버튼.
-> 자료 연동 애니메이션 로딩 페이지로 전환. 의료정보가 전담한의사에게 전달되었다고 알림 페이지. 확인버튼
-> 의료정보 연동 여부 알림톡 내 카톡으로 발송됨!
-> [의료정보 페이지] 체중변화 or 내 건강 2가지 페이지 전환 가능
-> [알림권한 허용 팝업 페이지] 튜토리얼처럼 하나씩 기능설명하고 넘어감
->[홈페이지] 진료예정, 감량결과, 식사/한약기록, 운동시작 버튼, 혈당관리 시작버튼, 앱 관련 사용방법 링크 버튼, 결과확인, 관련 게임연동 이동버튼, 식단관리 상품 광고 페이지 이동 버튼

하단 네비게이터 바 5개 페이지 전환
[홈 / 내 기록 / 메시지 / 후기,팁 / 내정보]
->[내 기록] Todo 앱과 비슷함. 기록하기 누르면 홈페이지로 전환되어서 식사/한약기록 으로 안내하고 알려주듯이 깜박임 기능,
식사등록 페이지 전환 시 사진앨범 연동 엑세스 허용안내문구
-> 사진을 찍으면 칼로리 계산하는 애니메이션 동작
->[메시지 페이지] 상담원과 채팅기능
->[후기, 팁 페이지] 상단에 목록 아이콘 선택하면 페이지 전환
->[내정보 페이지] > [진료신청 내역 페이지] 이동가능. (진료시간 / 예약변경 버튼-누르면 해당 한의원 전화 연결버튼)


✔️ 린 다이어트 기술 스택 및 구조 분석

[Todo기록 + 오프라인 연동 + 의료 데이터 활용 + 고도화된 입력 폼]

이 앱은 사용자의 민감한 정보(주민번호, 의료기록)를 다루기 때문에 보안외부 API 연동이 핵심

  • 인증 및 보안 (Auth & Security): * 카카오/네이버 간편인증 연동 (OAuth 2.0).

본인인증 모듈 (NICE, 다날 등 외부 PG/인증사 연동).

  • 복잡한 입력 폼: * 상태 관리(Provider, GetX, Bloc 등)를 통해 사용자가 입력한 방대한 문진 데이터를 임시 저장하고 단계별로 검증

조건부 렌더링: 특정 답변을 선택하면 하위 질문이 나타나는 로직.

  • 데이터 시각화 및 애니메이션: * Lottie 라이브러리: 진료 신청 완료, 분석 중 등 로딩 화면에서 높은 퀄리티의 애니메이션 구현.

Custom Painter 또는 Chart Library: 체중 변화 그래프.

  • 외부 연동: * Kakao Talk 채널 API: 상담원 연결.

Google/Apple HealthKit: 의료 정보 연동.
Vision API (추정): 식단 사진 촬영 시 음식 인식 및 칼로리 계산.


2. 기술적 핵심 역량 (나의 역할)

단순한 기능 구현을 넘어, 왜(Why) 이 기술을 선택했는지에 집중하여 설계해보았다.

계층형 설계 (클린 아키텍쳐):

유지보수와 확장성을 고려하여 데이터(Data), 도메인(Domain), 표현(Presentation) 계층을 분리하여 구조화

선언적 상태 관리 (리버팟):

초기 GetX 검토 후, 플러터의 위젯 생태계와 불변성(Immutability) 원리를 깊이 있게 학습하기 위해 Riverpod으로 전환하여 적용

데이터 반응형 UI:

StreamBuilder와 리버팟의 Notifier를 활용하여 데이터의 생성, 수정, 삭제가 사용자 화면에 실시간으로 반영되도록 구현

사용자 경험(UX) 최적화:

입력 폼의 유효성 검사(Validation)를 통해 실시간 피드백을 제공하고, Lottie(로티) 애니메이션으로 성취의 즐거움을 시각화


2. 구조에 대한 고민/목표

기능별 상세 기술 목표 (린다이어트 분석 반영)

① 로그인 및 온보딩 (Auth)

기술 목표: 소셜 로그인(구글/카카오) 성공 후, 유저 정보를 Firestore에 자동 저장.

이름 입력 안 하면 빨간 테두리 같은 Form Validation과 진행 표시바(LinearProgressIndicator)를 구현하면 사용자 경험(UX)향상에 도움이 될 것 같다.

한뼘 방식: 구글 로그인 하나로 끝내기. 대신, '나를 알아가는 질문' 3가지만 넣기 (예: "요즘 가장 지치는 순간은 언제인가요?", "나를 위해 하고 싶은 작은 일은?") -> 이 답변을 나중에 '내 정보'에서 다시 보여주기

② 데이터 CRUD (기록하기)

기술 목표: StreamBuilder 또는 GetX를 활용해 실시간으로 데이터 반영.

디테일 포인트: 식단 기록 시 사진 업로드(Firebase Storage) 기능을 넣고, 리스트에서 스와이프하여 삭제하거나 수정하는 기능을 구현

내 기록: 성장보고서에 정교한 수치 그래프

한뼘 방식: '오늘의 한 뼘 카드' 1~3개만 보여주기.
기능: Todo 체크박스 + 완료 시 Lottie 애니메이션 (축하 효과).
린다이어트의 '사진 찍기' 대신 '오늘의 나에게 한 줄 칭찬' 텍스트 입력으로 대체.
내 기록: '성취 달력'. 점이 찍히는 정도면 충분하다. 3개월 차 수준에서의 구현 기능으로 table_calendar 패키지를 사용해봄

③ 외부 API 및 복합 기능

기술 목표: 공공 데이터(날씨) API를 호출하여 오늘 운동하기 좋은 날씨인지 메인에 띄워주기.

디테일 포인트: 린다이어트의 '한의원 찾기'처럼 Google Maps API를 활용해 근처 운동 시설이나 병원을 지도에 표시하는 기능에 도전?

한뼘 방식: 유튜브 API 연동으로 명상 / 응원 영상
어렵다면 축하 / 응원문구를 미리 넣어두고 랜덤으로 출력하기


3. 꼭 들어가야 할 기술요소 (포트폴리오)

Firebase 연동 (CRUD): 사용자가 앱을 지웠다 깔아도 내 기록이 유지 (Firestore 사용)

상태 관리 (State Management): Provider나 Riverpod 중 하나를 정해서 사용하기(리버팟은 입문자에게 어려울 수 있으며 프로바이더를 먼저 사용해 보는 것도 좋다.) 앱 전체에서 사용자 데이터를 어떻게 관리했는지를 설명할 수 있어야 한다.

유효성 검사 (Validation): 이름을 입력하지 않으면 빨간 테두리가 생기는 로직은 TextFormField의 validator를 활용해 쉽게 구현 가능하며, 앱을 사용하는 사용자가 답답함이나 정상 동작하지 않는다고 느껴 앱 이탈률을 막기 위한 방법으로도 매우 중요하다.

  • 완성도를 높이는 기술적 포인트:
    Error Handling: API 호출 실패 시, 인터넷 연결이 끊겼을 때 등을 고려한 예외 처리(Try-Catch 및 UI 대응).
    Loading UI: 데이터를 가져오는 동안의 스켈레톤 UI나 로딩 스피너 구현.
    Animation: Hero 애니메이션이나 Lottie를 사용해 화면 전환을 부드럽게.

4. 개발 문화와 시스템

코드리뷰 등 의견을 정리하고 최종 의견을 결정하기
개발 튜터님, 디자인 튜터님 의견 조율
먼저 구상을 해보고 기본부터 만들고 이후 기능을 더하기


2. 프로젝트 제목 및 간단 설명

진행순서
[x] 1. 아이디에이션
[x] 2. 기능명세서(노션)
[x] 3. 메뉴트리 작성
[x] 4. 와이어프레임
[ ] 5. 디자인

1) 결정한 프로젝트 이름과 간단한 설명

'한 뼘'

카테고리: 건강 및 피트니스 App Store
최종프로젝트 S.A 노션 링크

와이어프레임 참조 링크
대시보드 참조 링크

2) 유저플로우 분석

피그마 링크

3. 3/16 중간발표회까지 작업한 것 - 최소 MVP 구현 범위

(내 앱에 맞춰서 해당 내용 수정하기)

MVP 스펙

[ ] 1. 로그인 / 회원가입

  • 소셜로그인(카카오 필수) / 구글
    -> 일단 내 폰 안에서 저장될 수 있도록

[ ] 2. 메인페이지

  • 게시판 진입 랜딩페이지

[ ] 3. 게시판 페이지

  • 게시글 (CRUD, 좋아요)
  • AI 기능 활용: TFLite YOLO 모델 활용한 이미지 분류 기능

[ ] 4. 마이페이지

  • 유저 정보 수정(프로필이미지, 닉네임)
  • 유저가 쓴 게시글 / 내 게시글에 달린 댓글 모아보기

[ ] 5. 검색

MVP 목표

  • 1, 2, 3, 4, 5 구현 완료하기
  • 최적화
  • Firebase연동
  • 클린아키텍쳐 사용 및 테스트코드 작성

회원가입,로그인 / 기록하기 CRUD / 홈화면, 대시보드
3가지를 클린 아키텍처로 구현하고, 에러 처리와 로딩 UI까지 매끄럽게 돌아가게 하기!

구현 가능성 및 기술 스택 매칭

UI/UX: Material 3 테마를 기본으로 하되, 린다이어트처럼 깔끔한 화이트/청록 톤 유지
상태 관리: GetX (채용 공고 우대사항 반영). 화면 전환과 상태 관리가 직관적이라 혼자 개발할 때 속도가 빠름
백엔드: Firebase. 별도의 서버 개발 없이 Auth, Firestore, Storage(이미지)를 모두 해결가능
네트워크: Dio 패키지. 날씨 API나 외부 API 연동 시 가장 많이 쓰이는 표준 라이브러리

중간발표까지의 현실적인 체크리스트

혼자하기 때문에 선택과 집중이 필요함. 중간발표 때는 "동작하는 뼈대"를 보여주는 것이 중요


3/5 목
와이어프레임 완성하고
파일, 레포지토리 생성
flutter pub add flutter_riverpod 리버팟 패키지 추가
flutter pub add google_fonts percent_indicator flutter_spinkit freezed_annotation json_annotation
구글폰트랑 프리즈드 패키지 추가

  1. 실제 앱 로직에 필요한 패키지
    flutter pub add freezed_annotation json_annotation
  2. 코드 생성을 도와주는 개발용 도구 (dev_dependencies)
    flutter pub add --dev build_runner freezed json_serializable

Freezed사용법
dart run build_runner build
또는 계속 감시하면서 자동으로 만들게 하려면
dart run build_runner watch

맥북 용량이 부족해서 시뮬레이터 동작이 안되는 바람에 용량이 큰 설치파일들을 지우다가 XCode를 잘못 건드려서 원인을 찾다가 재설치를 하고, 시간을 조금 낭비하게 되었다!

3/6 금

디자인 피드백
타겟층도 좀 더 명확히 좁혀보기
전체적인 플로우 다시 맞추기, 컬러는 나중에 테마 정해서 바꿔도 됨. 큰 틀은 개발먼저 하고, 캐릭터도 영역만 먼저 잡아넣고 나중에 추가

프로필 등록하기
이미지피커 패키지 추가

오늘도 원래 목표는 중간발표까지 가능할 부분이라고 생각이 되는 페이지까지 UI구현을 끝내려 했지만 생각보다 좀 오래 걸린다.
늦지만 한 뼘 씩 나아가며 프로젝트를 하는 나 자신에게 취지와 맞는 위로를 건네보았다.
에러를 바로바로 확인해가며 빠르게 적용해보고 문제를 해결하는 능력을 키우는 과정인 것인데 너무 완벽하게 구현을 하려는 욕심을 버려야 될 것 같다.

3/7 토

트러블슈팅

[ 문제 발생 ]

닉네임을 입력하고 등록하는 joinPage구현 중
실시간 피드백(입력 중)과 최종 검증(버튼 클릭 시) 로직이 섞여서 UI 밀림 현상이 발생했다.
textFormField 하단에 기본 에러 메시지가 출력되면서 _currentLength 텍스트를 밀어냄

검증 조건 로직은 ValidatorUtil에 두되, 상태 업데이트는 위젯에서 관리해야 한다.

[ 원인 ]

TextFormField는 자체적으로 에러 메시지를 표시하는 기능을 내장하고 있다!
별도의 Text로직을 만들어주지 않아도 됨
에러가 없을 땐 0픽셀이었다가, 에러가 생기면 메시지 높이(약 20픽셀)만큼 공간을 억지로 만들기 때문에 UI 밀림현상이 발생했다.

[ 문제 해결 과정 1 ]

TextFormField의 속성을 이용해서 수정을 했지만 에러메시지 UI 전체가 밀리는 현상 발생
에러발생 시 OutlineInputBorder 테두리가 빨간색으로 바뀌는 에러표시가 되어야 하는데 적용이 되지 않음

[ 원인 ]

TextFormField가 에러 메시지 출력 공간을 동적으로 확보하기 때문에 발생하는 문제
앱 테마를 정리해주기로 함. 현재 main함수에 테마를 적용해두었는데 별도의 파일로 분리해서 적용시켜야겠다.

[ 문제 해결 ]

sizedBox로 감싸서 크기값을 고정하는게 좋을까 싶었지만 자체기능을 활용하는 것이 코드를 더 간결하게 만들어준다.
에러스타일의 높이값을 8에서 1로 변경했다.

app_theme.dart파일을 main에서 작성했던 것을 별도로 분리한 뒤
에러체크를 가장먼저 하도록 순서를 바꿔주었다!

전체적인 테마 분위기나 디자인은 UI구현이 끝나면 추후 수정 예정


3/8 일

텍스트 테마 지정하고 적용하기

  • 라이트테마 / 다크테마 적용 시 AppTextTheme 클래스 안에서 직접 darkMode 변수를 체크하려고 하니 에러가 남.
    왜냐하면 static const는 앱이 실행되기도 전(컴파일 타임)에 값이 결정되어야 하는데,
    darkMode는 앱이 실행되는 중에 바뀌는 값이기 때문

[ 해결 방법 ]
두가지 테마를 따로 적용하기!
공통 스타일 적용(size, weight)


현재. 전역 설정 및 정적 리소스

static VS static get 변수
static final/const 변수: 선언 즉시 초기화되며, 로직을 포함할 수 없다. 단순한 고정값을 가짐
static get (Getter) 변수: 호출될 때 계산, 반환되며, => 뒤에 코드를 작성한다. 동적인 계산을 하고, GetX 연결

-> 테마 정보를 관리하거나 위젯에서 접근하는 방식

비교 1. GetX Controller로 인스턴스 접근하기

관리방식의 차이: 생명주기 관리! 해당 컨트롤러가 필요한 페이지가 닫히면(Pop) 컨트롤러도 메모리에서 자동으로 삭제된다!

생명주기
Get.put() 호출 시 생성되며
페이지 종료 시 삭제된다. (설정에 따라 유지 가능)
의존성 주입(DI) 방식이라 테스트가 쉽다.
Obx 등을 통해 실시간 상태 반영이 가능하다.

  • 싱글톤의 어디서든 접근 가능한 장점은 챙기면서, 유연한 메모리 관리와 반응형 UI를 동시에 사용 가능한 방법
  • 상태관리를 위한 전역공간으로 class로 구현되는 Page나 widget끼리 데이터를 주고 받을 때 context나 인자를 주고받아야 하는데 GetX는 그럴 필요가 없다.
    뷰 widget -> 뷰 pages
    뷰 widget -> 로직 controller -> 뷰 Pages

비교 2. 싱글톤 패턴 인스턴스 가져오기

관리방식의 차이: 한번 생성되면 계속 메모리에 상주함. 앱이 꺼질 때 까지 절대 삭제되지 않음
단점: 사용하지 않는 페이지에서도 메모리를 계속 차지하며, 상태를 초기화하려면 수동으로 로직을 짜야된다.

-> GetX Controller로 테마 관리
ThemeController에 담아 GetX로 관리하는 것이 프로젝트의 유지보수 측면에서 훨씬 좋다.
-> 테마관리 외 상태관리, 비즈니스로직, 라우팅(Navigator.push 대신 Get.to(() => NextPage()) 처럼 훨씬 간결하게 페이지를 이동할 때 씀)에서도 사용됨

-> 기존 라이브러리보다 자유도가 높아서 코드짜는 방식이 다 다르다.
그리고 테마 컨트롤러에 대한 부분에 대해서도 설명할 수 있어야 하고, 상태관리에 대해서 배우는 과정인데 사용은 편리하지만 지금 학습과정과 요구하는 방향이 맞지 않아서 Theme구현에 GetX를 적용해보고 다시 리버팟으로 변경하였다.


상태 관리 라이브러리 전환 (GetX → Riverpod)

문제: 전역 상태 관리 중 테마 변경이 실시간으로 UI에 반영되지 않음.

원인: 단순 읽기 방식인 ref.read를 사용하여 상태 변경 감지 실패.

해결: 구독 방식인 ref.watch로 변경하여 테마 전환 시 빌드 메서드가 재실행되도록 수정했습니다. 이 과정을 통해 프레임워크의 렌더링 원리를 심화 학습

비교 3. Riverpod 라이브러리 패키지

리버팟 패키지 설치 링크

GetX와 Riverpod의 차이

GetX: 위젯 트리와 별개로 메모리에 인스턴스를 띄워두고 접근한다. 싱글톤 방식
Riverpod: 선언적 상태 관리. 상태가 변하면 해당 상태를 구독(Watch)하고 있는 위젯만 다시 그려낸다.
-> GetX는 편리하지만 Riverpod이 플러터의 위젯 생태계와 불변성, 의존성 주입에 대한 원리를 공부하기엔 좋다.

Riverpod + Freezed 조합이 최고!

리버팟 스니펫
주로 VS Code에서 Riverpod 관련 코드를 신속하고 정확하게 자동 완성하여 개발 속도를 향상시키고 반복 코드를 줄이는 데 사용
반복 코드 자동 완성: Provider, ConsumerWidget, StateNotifier 등 매번 새로 작성하기 번거로운 코드 구조를 몇 글자의 약어(Shortcut)로 빠르게 생성

리버팟은 상태관리 헬퍼로 앱의 모든 부분에서 상태(변수값)에 접근할 수 있으며, 위젯트리 최상위에 상태를 배치하고 상태변경을 감지해 UI를 업데이트 한다.
제공되는 프로바이더는 상태의 일부를 캡슐화하고 그 상태를 수신할 수 있게 하는 객체다.
상태(State)가 변하면 이를 구독하는 위젯들이 자동으로 다시 그려지는 원리를 이용

리버팟(Riverpod)의 기본 구조-3단계

  1. 앱 최상단을 ProviderScope로 감싸 상태(State)를 관리하고
  2. 전역적으로 정의된 Provider를 통해 상태를 보관하며
  3. ConsumerWidget과 WidgetRef(ref.watch/read)를 이용해 위젯에서 상태를 읽고 업데이트한다.

Provider 정의 (상태 캡슐화)
어디서든 접근 가능한 전역 변수로 Provider를 생성
상태의 종류에 따라 Provider (읽기 전용), StateProvider (단순 상태 변경), NotifierProvider (복잡한 로직) 등을 사용한다.

테마 구조: 조립 순서

AppTextTheme: 글자 크기, 굵기만 정의
AppColorTheme: 색상만 정의
AppTheme: ColorScheme.fromSeed를 사용해 라이트/다크 색상을 만듦
AppTextTheme, AppColorTheme을 불러와서 합친다.
버튼, 입력창 스타일을 최종적으로 조립
ThemeController (배달부):
위에서 완성된 AppTheme.lightTheme에서 색상만 뽑아서 쓰거나, 텍스트만 뽑아서 위젯에 전달하는 역할


JoinPage 테마 적용 및 리버팟 전환

StatefulWidget -> ConsumerStatefulWidget 전환
ref.watch(themeProvider)를 사용해서 테마 데이터 받아오기


  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;

  Widget build(BuildContext context) {
    final textTheme = Theme.textTheme;

[ 문제 발생 ]

JoinPage에서 테마를 적용시키려고 했으나 에러가 발생

[ 원인 ]

프로바이더의 역할에 대한 이해가 필요하다!
기본 상태관리로 상태변화가 필요한 경우는 Provider를 사용
-ref.watch()로 변경 사항을 감지할 수 있지만, 데이터 자체를 변경할 수는 없음
상태 변화는 필요한데 로직이 단순한 상태관리에는 StateProvider를 사용
복잡한 로직의 상태변화에는 NotifierProvider를 사용

상태가 변하지 않는 읽기전용 데이터인 ThemeData를 제공할 때는 일반 Provider를 사용해야 된다.

아래에선 컬러와 텍스트 프로바이더에 스테이트프로바이더를 반환값으로 담아주고 있다.

이 때, 그냥 프로바이더로 변경하면 에러가 뜨게 되는데 에러메시지의 내용을 보면 Provider으로 취급을 하고 있음.
타입이 올바르게 명시되지 않았다는 뜻
그래서 위젯에서 .textTheme속성을 찾지 못했다.

[ 해결 방법 ]

ColorScheme이나 TextTheme뿐만 아니라 두가지가 통합된 ThemeData 객체를 만들어주면 된다.
객체를 만들고 반환타입을 명시해주기

이렇게 하면 컬러와 텍스트 프로바이더를 각 불러올 필요 없이 themeProvider하나만 watch하면 된다.


[ 문제 발생 ]

테마 변경이 실시간으로 되지 않는 이유는?

[ 원인 ]

ref.read를 사용해서 그렇다.
값을 한번만 읽고 끝내기 때문에 테마가 끝나도 위젯이 다시 그려지지를 않음

[ 문제 해결 ]

ref.watch를 사용해야 값이 바뀌는지 계속 지켜보고, 테마변경 시 빌드메서드를 다시 실행해서 UI를 업데이트 해준다!


NickNameTextFormField 프로바이더 관리

기존 리버팟 사용 전 StatefulWidget위젯 내에서 TextEditingController를 리스너를 적용해서 관리하고 있었는데

joinPage도 리스너로 관리중이었는데 setState를 호출해서 화면전체를 다시 그려야했고, dispose로 리스너를 해제하지 않으면 메모리 누수의 위험이 있었다.
기능을 분리해서 리버팟의 Notifire를 사용하자. ConsumerWidget으로 변경하면 닉네임 데이터를 앱 전체 상태로 관리하므로 나중에 등록버튼을 누를 때에도 값을 가져오기 편해진다.
코드가 훨씬 간결해지는 장점도 있다.

Notifier는 MVVM패턴에서 뷰모델의 역할을 수행함!

  • 스테이트풀위젯에 들어있던 데이터와 로직을 위젯에서 분리해서 별도 클래스로 관리하기
    View: UI를 그리고 사용자 입력을 받는 화면 (JoinPage, NickNameTextFormField)
    ViewModel: 데이터 로직을 다룸 UI로 상태를 전달 (Provider로 파일 분리)
    Model: 실제 저장되는 데이터 구조 (User클래스)

리버팟 상태관리를 위해 기존 TextEditingController를 사용해서 리스너를 등록하고 GlobalKey로 실시간 로직을 처리하였는데 _nickNameControllor를 뷰모델 클래스로 옮기고, 기존 컨트롤러는 삭제해서 비즈니스 로직과 UI를 분리해서 조금 더 간결하게 상태관리가 가능해졌다.

NotifierProvider (동기식)

용도: 복잡한 상태 객체, CRUD 로직, 동기식 데이터 변경, 뷰모델(ViewModel)로 사용.

AsyncNotifierProvider (비동기식)

용도: API 통신, 데이터베이스 연동 등 비동기 데이터를 다루는 뷰모델(ViewModel).

부가 기능 (Modifiers)

.autoDispose: 화면이 사라지면(더 이상 사용되지 않으면) 상태를 자동으로 해제하여 메모리를 절약 (예: NotifierProvider.autoDispose)


최신 Riverpod (Generator) 구조 및 에러 원인

Riverpod 3.0(및 2.x 최신)에서는 개발자가 직접 Provider를 선언하기보다, 클래스 위에 @riverpod 어노테이션을 달고 빌드 러너를 돌려 코드를 생성하는 방식을 권장

최신방식은 어노테이션 사용:

자동완성 매크로 빠른개발이 목적이나 불안정해서 잘 쓰지 않는다.
관련 문서 참조 링크

• 단순한 상태: 원시 타입 하나만 관리 → StateProvider 사용
• 일회성 비동기: 단순 API 호출 → FutureProvider 사용
• 실시간 스트림: 지속적 데이터 변화 → StreamProvider 사용


[ 문제 발생 및 해결 ]

@riverpod을 사용하지 않고 StateNotifier변경 시
코드를 생성하는 방식인 @riverpod은 build()를 통해 초기값을 반환하지만,

StateNotifier는 생성자를 통해 부모에게 전달해야 한다!
전달하는 방법 :super

하지만, 상단의 import경로가 초기버전을 지원하는 legacy.dart로 되어있어서 클래스간 충돌을 막기 위해 정상 경로로 바꾸었다.
리버팟3.0 부터는 StateNotifier 사용을 지양하고 있어서 에러가 발생했고, Notifier로 변경. (최신버전부터는 AutoDisposeNotifier클래스도 다 Notifier에 통합되어 있음)
Notifier는 생성자를 쓰지 않고 build()를 오버라이드한다.

NotifierProvider도 마찬가지로 수정해주었다.


ConsumerStatefulWidget을 ConsumerWidget으로 변경하는 이유는 상태는 이미 뷰모델에 있기 때문에 단순하게 변경 가능!


페이지 전환

사용자 등록이 등록버튼을 눌러서 완료되면 joinPage에서 홈 화면으로 넘어가기
방법 1. UI에서 상태변화를 감지해서 ref.listen으로 받아와서 이동하기
방법 2. 뷰모델에서 네비게이터를 이용해 처리하기
-> 복잡한 상태반응을 반영해야 한다면 1번을 추천. 에러 팝업, 성공하면 이동

GoRouter 네비게이션 패키지 사용

고라우터 사용법 링크
context.go() 또는 context.pushReplacement()를 사용하여 스택을 관리할 수 있다.
회원가입 성공 후에는 뒤로 가기 버튼을 눌러 다시 가입 페이지로 돌아오면 안 되므로, context.go('/home')을 사용하여 네비게이션 스택을 초기화하는 것이 정석
중첩 라우팅기능으로 바텀네비게이션 바 또는 탭 컨트롤러랑 함께 사용함

GoRouter 자체를 Provider로 관리

파라미터가 필요한 경로가 있으면 추가. 페이지 이동 시 특정 데이터(ID, 이름, 카테고리 등) 함께 넘겨줘야 될 때
예를 들어, 쇼핑몰 앱에서 단순히 '상품 목록' 페이지로 가는 게 아니라, '3번 상품의 상세 정보' 페이지로 가고 싶을 때 상품 번호인 3이 바로 파라미터가 됨


로그인 여부에 따라 첫 페이지가 다르게

Redirect(리다이렉트) 로직

로그인 상태관리는 나중에 하고

  1. 파라미터 전달의 두 가지 방식
    GoRouter에서는 크게 두 가지 방식으로 데이터를 전달합니다.

① Path Parameters (경로 파라미터)
URL의 일부로 데이터를 포함시키는 방식입니다. 필수적인 데이터를 전달할 때 주로 사용합니다.

형태: /details/:id

예시: /details/101, /details/apple

② Query Parameters (쿼리 파라미터)
URL 뒤에 ?를 붙여서 선택적인 데이터를 넘기는 방식입니다. 검색 필터나 정렬 기준에 자주 쓰입니다.

형태: /search?keyword=flutter


홈페이지 UI작업하기

  • 투두 모델, 뷰모델 파일 생성
  • intl 0.20.2 패키지 설치(DataTime 표기 예쁘게 해줌)
  1. ... (Spread Operator) 스프레드 연산자의 역할
    "기존 리스트의 알맹이(요소)들을 하나씩 꺼내서 새로운 리스트에 뿌려주는 역할"

기존 방식: 리스트를 만들고 add()를 호출 (원본을 수정)
state.add(newTodo)

스프레드 방식: 기존 리스트의 요소들을 복사해서 새로운 리스트를 생성 (불변성 유지)

  1. 왜 state = [...state, newTodo]; 라고 쓸까?
  • Riverpod의 핵심 원리

새로운 인스턴스 생성: [] 대괄호를 새로 열었기 때문에 기존 리스트와는 다른 새로운 메모리 주소를 가진 리스트가 만들어짐

기존 데이터 복사: ...state를 통해 기존에 있던 투두 항목들을 새 리스트에 다 집어넣는다.

새 데이터 추가: 그 뒤에 newTodo를 붙인다.

상태 교체: state =를 통해 새로운 리스트로 통째로 갈아 끼운다.

단순히 add()를 쓰면 리스트 내부의 값은 변하지만, 리스트의 주소값은 그대로다. 그러면 Riverpod은 상태가 변했다고 인식하지 못해 UI를 새로 그리지 않는다.
반면, 스프레드 연산자를 사용하면 기존 요소를 포함한 새로운 리스트 객체를 만들기 때문에, 주소값이 바뀌면서 상태 변경이 정확히 감지되고 안정적인 UI 업데이트가 가능해진다!


  1. 실무 활용 팁 (필터링/정렬)
    나중에 완료된 항목을 리스트 맨 뒤로 보내거나 필터링할 때도 응용할 수 있다.
// ex) 완료되지 않은 것들 먼저, 그 뒤에 완료된 것들 배치
state = [
  ...state.where((todo) => !todo.isDone),
  ...state.where((todo) => todo.isDone),
];

3/13 금 다시 프로젝트 기술방향 수정 스테이트풀 위젯 구현

프로젝트 초기에는 최신 상태 관리 라이브러리인 Riverpod 도입을 시도했으나, 현재 프로젝트의 규모와 복잡도를 고려했을 때 기본 제공되는 StatefulWidget만으로도 충분히 명확하고 빠른 구현이 가능하다고 판단했습니다. 불필요한 라이브러리 의존성을 줄이고, 플러터의 기본 동작 원리에 집중하여 프로젝트를 안정적으로 완성하는 것에 우선순위를 두었다.

profile
앱 개발을 공부중입니다.

0개의 댓글