카테고리 없음

Flutter 마켓 앱 Chapter 5 — 실무형 REST + GPS + Docker + HomeTab 완성

kwontete 2025. 11. 12. 21:06

학습 목표 요약

구분핵심 내용기술 포인트
VWORLD 연동 위치 좌표 기반 주소 매칭 REST API 요청, 위도/경도 변환, async 처리
GPS 연동 기기 위치 실시간 추적 geolocator, permission_handler
REST API 구조 이해 HTTP 요청의 흐름 파악 GET/POST/PUT/DELETE, header & body
Docker 환경 구축 로컬 백엔드 환경 세팅 MySQL + Spring Boot 컨테이너 연결
인증/회원가입 기능 HTTP 요청 + JWT Token 구조 이해 Dio 통신, 로그인 후 Token 저장
내 정보 가져오기 Token 기반 인증 요청 Authorization Header 설정
내 동네 불러오기 REST API 응답 파싱 → 모델화 fromJson / toJson 구조
상품 목록 불러오기 Address → Product 연동 비동기 상태관리, Riverpod Notifier
HomeTab 완성 상태 흐름 최적화 ViewModel 구조화 + UI 연동

학습 흐름 정리

1. VWORLD & GPS 연동

  • VWORLD API로 지도상의 좌표를 주소로 변환
  • geolocator 패키지를 사용해 실시간 위치 가져오기
 
final position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high, );

핵심 포인트

  • permission_handler로 위치 권한 요청
  • async/await 구조에서 예외 처리 중요

2. REST API 구조 및 연결

  • REST의 4대 메서드: GET, POST, PUT, DELETE
  • Dio를 이용해 서버 통신 구현
 
final response = await dio.get('$host/api/products');

핵심 포인트

  • 헤더(Header)에 JWT Token을 포함해야 인증 성공
  • 응답(Response) 구조에 맞는 데이터 모델 설계

3. Docker 기반 로컬 서버 세팅

  • Mac과 Windows 환경 모두에서 Spring Boot + MySQL 연결
  • .env 파일에 포트 및 계정 정보 설정

핵심 포인트

  • 컨테이너 간 통신 시 포트 충돌 주의
  • docker-compose up 실행 후 서버 자동 부팅 확인

4. 인증 / 로그인 / 회원가입

  • HTTP 요청으로 이메일·비밀번호 전달
  • 성공 시 JWT 토큰을 받아 SharedPreferences에 저장
 
await storage.write(key: 'accessToken', value: token);

핵심 포인트

  • 로그인 후 API 요청 시 Authorization 헤더 필요
  • Bearer + token 형식으로 전달

5. 내 정보 & 내 동네 가져오기

  • /api/user/me → 사용자 정보 조회
  • /api/address → 주소 리스트 응답
 
final response = await client.get("$host/api/address"); final addresses = List.of(response.data['content']) .map((e) => Address.fromJson(e)) .toList();

핵심 포인트

  • fromJson 모델링을 통해 코드 가독성 향상
  • defaultYn 속성으로 대표 주소 관리

6. 상품 목록 & HomeTab 완성

  • HomeTabViewModel에서 주소 → 상품 순으로 비동기 호출
  • Riverpod의 Notifier 구조로 상태 관리
 
class HomeTabViewModel extends AutoDisposeNotifier<HomeTabState> { final addressRepository = AddressRepository(); final productRepository = ProductRepository(); @override HomeTabState build() { fetchAddress().then((_) => fetchProducts()); return const HomeTabState(addresses: [], products: []); } }

핵심 포인트

  • ViewModel에서 API 호출 후 copyWith()로 상태 갱신
  • AutoDispose로 메모리 관리 자동화

7. intl 패키지로 데이터 포맷팅

 
NumberFormat('###,###원').format(product.price);

핵심 포인트

  • 금액, 날짜 등 UI 표기를 지역화(Localization) 지원
  • 한국형 포맷(원 단위, yyyy-MM-dd) 적용

트러블 슈팅 기록

구분문제원인해결
GPS 권한 거부 시 위치 미수신 권한 미요청 iOS Info.plist 및 AndroidManifest에 권한 선언 누락 플랫폼별 권한 요청 코드 추가
API 응답 null 로그인 누락으로 인증 실패 JWT 토큰 미전달 Dio header에 Authorization: Bearer token 추가
Docker 연결 오류 포트 충돌 MySQL 기본 포트 중복 .env 파일 포트 변경
리스트 로딩 지연 비동기 실행 순서 문제 주소보다 상품 요청이 먼저 실행 fetchAddress().then((_) => fetchProducts())로 순차 처리
intl MissingPackage Error 패키지 누락 flutter pub add intl 실행  

배운 점 정리

  • Repository 패턴으로 ViewModel의 역할이 단순해지고 유지보수성이 향상됨
  • Notifier 구조는 UI와 로직을 완전히 분리시켜 테스트와 수정이 용이함
  • AutoDispose는 화면 단위 메모리 관리에 매우 효과적
  • Freezed + Riverpod + Hook 조합으로 불변 상태와 직관적인 코드 구현 가능
  • REST 구조를 완벽히 이해하고 로컬(Docker) 환경에서 서버 통신 테스트까지 수행

인사이트

“데이터는 Repository에서,
상태는 ViewModel에서,
렌더링은 View에서.”

이 원칙이 유지될 때 Flutter 프로젝트는 복잡해져도 무너지지 않는다.
이번 챕터를 통해 Flutter + REST + 상태관리의 통합 구조를 완전히 이해했다.