학습 목표 요약
구분핵심 내용기술 포인트
| 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 + 상태관리의 통합 구조를 완전히 이해했다.