Skip to content

[NDGL-88] 홈 화면 API 연결#21

Merged
jihee-dev merged 7 commits into
developfrom
feature/NDGL-88/impl-home-api
Feb 16, 2026
Merged

[NDGL-88] 홈 화면 API 연결#21
jihee-dev merged 7 commits into
developfrom
feature/NDGL-88/impl-home-api

Conversation

@jihee-dev
Copy link
Copy Markdown
Member

@jihee-dev jihee-dev commented Feb 16, 2026

NDGL-88 홈 화면 API 연결


연관 문서

디자인

변경사항

  • 홈 화면 API 연동

    • 다가오는 여행 API 연동: UserTravelApi, UserTravelRepository 신규 추가
      • 기존 더미 데이터 대신 실제 API로 다가오는 여행 데이터를 조회하도록 변경
    • 인기 여행 템플릿 조회 API 연동: TravelTemplateApi, TravelTemplateRepository 신규 추가
      • 인기 템플릿 목록을 실제 API에서 가져오도록 변경
    • 추천 여행 템플릿 조회 API 연동: 추천 템플릿 관련 API 호출 추가 (getRecommendTravelTemplates)
    • 여행 프로그램 전체 목록 조회 API 추가: TravelProgramApi, TravelProgramRepository 신규 추가
  • 데이터 모델 재구성

    • 기존 InProgressTravel, TravelSummary, HomeRepository 삭제 (더미 기반 모델/레포)
    • API 응답에 맞는 새 모델 추가
    • LocalDateSerializer 추가: API 날짜 필드(LocalDate) 직렬화/역직렬화 처리
  • 네트워크 모듈 리팩토링

    • Retrofit 인스턴스 생성을 NetworkModule로 통합: 각 feature 모듈에서 개별 생성하던 Retrofit을 core NetworkModule에서 단일 제공하도록 변경
    • TravelNetworkModule 추가: Travel 관련 API 인터페이스들의 DI 바인딩 담당
  • UI 업데이트

    • HomeScreen: status bar 패딩 적용, API 연동에 따른 상태 관리 변경
    • HomeViewModel: 3개 API를 병렬 호출하고 결과를 UI 상태에 반영하도록 로직 개편
    • ResourceUtil 추가: PlaceCategory → drawable 리소스 매핑 유틸
  • 인프라/설정

    • HTTP 로그 Pretty Print 적용: OkHttp 로깅 인터셉터에서 JSON을 가독성 좋게 포맷
    • lint.xml 추가: UnsafeOptInUsageError 규칙에 kotlinx.serialization 실험적 API opt-in 등록

테스트 체크 리스트

  • 다가오는 여행 조회
  • 인기 여행 조회
  • 추천 여행 조회

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 여행 프로그램별 필터링 기능 추가 (전체/개별 프로그램 선택)
    • 예정된 여행 정보 조회 기능 추가
    • 장소 카테고리 구분 (공항, 교통수단, 관광지 등)
    • 인기 여행 템플릿 및 추천 여행 콘텐츠 API 추가
  • 개선 사항

    • 여행 데이터 구조 개선 (프로그램 기반으로 재설계)
    • 날짜 처리 최적화
    • HTTP 로깅 및 네트워크 통신 강화

@jihee-dev jihee-dev self-assigned this Feb 16, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 16, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

기존 홈 화면의 여행 데이터 모델을 여행 프로그램 기반으로 재구조화하고, 로컬 모의 데이터에서 실제 API 호출로 전환했습니다. 네트워크 계층 인프라를 개선하고, 새로운 직렬화 및 저장소 계층을 추가하며, UI 컴포넌트를 새로운 데이터 모델에 맞춰 업데이트했습니다.

Changes

Cohort / File(s) Summary
String Resources
core/ui/src/main/res/values/strings.xml
공항 타입에 대한 새로운 문자열 리소스 place_type_airport 추가.
Network 및 DI Configuration
data/core/src/main/java/com/yapp/ndgl/data/core/di/NetworkModule.kt, data/travel/src/main/java/com/yapp/ndgl/data/travel/di/TravelNetworkModule.kt
HTTP 로깅 인터셉터를 주입 가능한 컴포넌트로 분리, Retrofit 설정 추가, 새로운 호출 어댑터 팩토리 통합, TravelNetworkModule을 통한 세 API 구현체의 싱글톤 제공.
Serialization 및 Configuration
data/core/src/main/java/com/yapp/ndgl/data/core/serializer/LocalDateSerializer.kt, lint.xml
LocalDate용 kotlinx 직렬화 serializer 추가, Kotlin 직렬화 내부 API 옵트인 설정.
Travel Data Models
data/travel/src/main/java/com/yapp/ndgl/data/travel/model/{PlaceCategory, ProgramType, TravelProgram, TravelTemplateSummary, PopularTravelTemplates, RecommendTravelTemplates, UpcomingTravelResponse, UserTravelPlace}.kt
새로운 여행 데이터 모델 추가: 장소 카테고리 열거형, 프로그램 타입, 여행 프로그램, 템플릿 요약, 응답 래퍼 클래스들. 모두 kotlinx Serializable 어노테이션 적용.
Removed Travel Models
data/travel/src/main/java/com/yapp/ndgl/data/travel/model/{InProgressTravel, TravelSummary}.kt
기존 모의 데이터 모델 제거: InProgressTravel, TravelSummary 및 중첩 타입.
Travel API Interfaces
data/travel/src/main/java/com/yapp/ndgl/data/travel/api/{TravelProgramApi, TravelTemplateApi, UserTravelApi}.kt
세 개의 새로운 Retrofit API 인터페이스: 여행 프로그램 목록 조회, 인기/추천 여행 템플릿 조회, 다가올 여행 조회.
Travel Repository Layer
data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/{TravelProgramRepository, TravelTemplateRepository, UserTravelRepository}.kt, data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/HomeRepository.kt (삭제)
새로운 저장소 계층: API 인터페이스를 래핑하고 데이터 추출 로직 제공. 기존 HomeRepository 제거.
Home Feature Contract
feature/home/src/main/java/com/yapp/ndgl/feature/home/main/HomeContract.kt
HomeState 재구조화: 여행 프로그램 탭, 프로그램별 인기 여행, 필터링된 여행 목록 추가. TravelContent 모델 도입, PopularTravelTab 제거, TravelPlace의 category를 PlaceCategory 열거형으로, estimatedTime을 estimatedDuration(Int)으로 변경.
Home Feature ViewModel
feature/home/src/main/java/com/yapp/ndgl/feature/home/main/HomeViewModel.kt
HomeRepository 제거, 세 개의 새로운 저장소 주입. loadMyTravel()을 UserTravelRepository 기반으로 전환하고 시간 기반 여행 상태 로직 추가(Upcoming/InProgress/None). loadPopularTemplatesByPrograms()으로 프로그램별 템플릿 동시 로드, TravelContent 매핑 추가.
Home Feature UI Components
feature/home/src/main/java/com/yapp/ndgl/feature/home/main/{HomeScreen, PopularTravelSection, RecommendedContentSection, UpcomingTravelCardSection}.kt
데이터 바인딩을 TravelSummary에서 TravelContent로 전환, 탭 모델을 PopularTravelTab에서 TravelProgramTab으로 변경. PlaceCategory 열거형 및 estimatedDuration 반영. MyTravelCardSection을 UpcomingTravelCardSection으로 이름 변경. null 체크 및 새로운 필드 사용을 위한 UI 업데이트.
Home Feature Utilities
feature/home/src/main/java/com/yapp/ndgl/feature/home/util/ResourceUtil.kt
PlaceCategory 및 ProgramType을 리소스 ID에 매핑하는 확장 함수 추가.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 주요 변경 사항을 명확하게 반영하고 있습니다. 'NDGL-88 홈 화면 API 연결'은 홈 화면을 더미 데이터에서 실제 API 연동으로 전환하는 이 PR의 핵심 목적을 정확히 설명합니다.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into develop

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/NDGL-88/impl-home-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In
`@data/travel/src/main/java/com/yapp/ndgl/data/travel/api/TravelTemplateApi.kt`:
- Around line 9-18: The getRecommendTravelTemplates method in TravelTemplateApi
is using the same endpoint as getPopularTravelTemplates, causing ambiguity;
update the annotation on getRecommendTravelTemplates to a distinct path (e.g.,
change `@GET`("/api/v1/travel-templates/popular") to
`@GET`("/api/v1/travel-templates/recommend")) or alternatively add a
distinguishing query parameter to the getRecommendTravelTemplates signature so
the backend can differentiate requests; locate TravelTemplateApi and update the
`@GET` annotation for the getRecommendTravelTemplates method accordingly.

In
`@data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/TravelProgramRepository.kt`:
- Around line 8-9: TravelProgramRepository is missing the `@Singleton` annotation
while UserTravelRepository is annotated; add `@Singleton` to the
TravelProgramRepository class declaration (above the class) so the class
TravelProgramRepository (the `@Inject` constructor with travelProgramApi:
TravelProgramApi) is provided as a singleton by the DI container, matching
UserTravelRepository's lifecycle.

In `@feature/home/src/main/java/com/yapp/ndgl/feature/home/main/HomeScreen.kt`:
- Around line 66-70: Scaffold의 contentPadding에서 하단에 하드코딩된 100.dp만 사용해
innerPadding이 무시되고 있으니 HomeScreen의 Scaffold 호출부에서 contentPadding의 bottom 값을
innerPadding.calculateBottomPadding()와 기존 100.dp를 더한 값으로 변경하세요 (즉 contentPadding
= PaddingValues(top = innerPadding.calculateTopPadding() + 20.dp, bottom =
innerPadding.calculateBottomPadding() + 100.dp)). 이렇게 하면 시스템 내비게이션 바나 향후
BottomBar 추가 시 하단 여백이 올바르게 반영됩니다.

In `@feature/home/src/main/java/com/yapp/ndgl/feature/home/main/HomeViewModel.kt`:
- Around line 163-171: The loadRecommendedTravel function uses
suspendRunCatching(...).onSuccess but omits .onFailure, so failures are ignored;
update loadRecommendedTravel to chain .onFailure after suspendRunCatching {
travelTemplateRepository.getRecommendTravelTemplates() } and handle errors (at
minimum log the exception via your logger or viewModelScope logger and
optionally update UI state), referencing the existing symbols
travelTemplateRepository.getRecommendTravelTemplates(), suspendRunCatching, and
reduce { copy(recommendedContents = ...) } so that errors are reported similarly
to loadMyTravel/loadPopularTemplates.

In
`@feature/home/src/main/java/com/yapp/ndgl/feature/home/main/PopularTravelSection.kt`:
- Around line 159-172: The UI is currently showing the raw country ISO code
(travel.country) alongside its flag emoji in PopularTravelSection Row; replace
that ISO code with a human-readable country name (or mirror the
RecommendedContentSection/ CountryChip behavior by showing travel.city) to keep
UX consistent. Update the Text that now uses travel.country to instead call a
utility that converts the ISO code to a localized country name (e.g.,
getDisplayCountry(travel.country) or a countryNameMap) or simply use travel.city
if that matches other sections; locate the Row/Text using travel.country and
toFlagEmoji() in PopularTravelSection and change the second Text's source
accordingly.

In `@lint.xml`:
- Around line 3-5: Remove the global opt-in for InternalSerializationApi from
the UnsafeOptInUsageError rule in lint.xml by editing the <option name="opt-in"
value="..."> entry so it no longer includes
kotlinx.serialization.InternalSerializationApi; leave
kotlinx.serialization.ExperimentalSerializationApi only if you truly need it,
and instead of a global lint opt-in add
`@OptIn`(ExperimentalSerializationApi::class) directly on the specific
classes/functions that require it to minimize scope and ensure
InternalSerializationApi usage will be detected.
🧹 Nitpick comments (14)
data/travel/src/main/java/com/yapp/ndgl/data/travel/model/PlaceCategory.kt (1)

6-25: @SerialName 값이 enum 상수명과 동일하여 불필요합니다.

kotlinx.serialization은 기본적으로 enum 상수의 이름을 직렬화 이름으로 사용합니다. @SerialName("AIRPORT") 등은 상수명과 동일하므로 제거해도 동작이 같습니다. 향후 API 응답의 값이 달라질 가능성을 대비한 것이라면 유지해도 무방합니다.

data/travel/src/main/java/com/yapp/ndgl/data/travel/model/ProgramType.kt (1)

6-13: API에서 새로운 프로그램 타입이 추가될 경우 역직렬화 실패 가능성을 고려해 주세요.

현재 YOUTUBETV 두 가지만 정의되어 있습니다. 서버에서 새로운 타입(예: PODCAST)을 반환하면 kotlinx.serialization이 예외를 발생시킵니다. 필요에 따라 coerceInputValues = true 설정과 함께 기본값 처리를 추가하거나, UNKNOWN 상수를 두는 것을 고려해 볼 수 있습니다. PlaceCategory에도 동일하게 적용됩니다.

data/core/src/main/java/com/yapp/ndgl/data/core/serializer/LocalDateSerializer.kt (1)

12-24: DateTimeFormatter.ISO_LOCAL_DATE 사용을 고려해 주세요.

"yyyy-MM-dd" 패턴은 DateTimeFormatter.ISO_LOCAL_DATE와 동일합니다. 표준 상수를 사용하면 의도가 더 명확해지고 커스텀 포매터 생성을 생략할 수 있습니다.

♻️ 제안하는 변경
 object LocalDateSerializer : KSerializer<LocalDate> {
-    private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
+    private val formatter = DateTimeFormatter.ISO_LOCAL_DATE
 
     override val descriptor: SerialDescriptor =
feature/home/src/main/java/com/yapp/ndgl/feature/home/util/ResourceUtil.kt (1)

16-19: YOUTUBETV가 동일한 아이콘(ic_20_video)에 매핑되고 있습니다.

현재 두 ProgramType 모두 같은 아이콘을 사용하고 있습니다. 의도된 것이라면 괜찮지만, 향후 TV 전용 아이콘이 추가될 예정이라면 TODO 주석을 남겨두는 것이 좋겠습니다.

data/core/src/main/java/com/yapp/ndgl/data/core/di/NetworkModule.kt (1)

67-70: 로깅 인터셉터 순서를 확인하세요.

httpLoggingInterceptorNDGLInterceptor보다 먼저 추가되어 있어, 로깅 시점에 NDGLInterceptor가 추가하는 헤더(인증 토큰 등)가 로그에 포함되지 않습니다. 디버깅 시 완전한 요청/응답을 확인하려면 로깅 인터셉터를 마지막에 추가하는 것이 일반적입니다.

인증 토큰 노출 방지가 목적이라면 현재 순서가 맞지만, 그렇지 않다면 순서를 변경하세요.

제안
 val builder = OkHttpClient.Builder()
-    .addInterceptor(httpLoggingInterceptor)
     .addInterceptor(interceptor)
     .authenticator(authenticator)
+    .addInterceptor(httpLoggingInterceptor)
feature/home/src/main/java/com/yapp/ndgl/feature/home/main/UpcomingTravelCardSection.kt (2)

149-151: DateTimeFormatterremember로 감싸세요.

DateTimeFormatter.ofPattern()이 Composable 함수 내에서 매 recomposition마다 새로 생성됩니다. remember를 사용하여 불필요한 재할당을 방지하세요. Line 219-221의 동일한 패턴에도 적용됩니다.

제안
-                val dateFormatter = DateTimeFormatter.ofPattern(
-                    stringResource(R.string.home_my_travel_card_date_format),
-                )
+                val pattern = stringResource(R.string.home_my_travel_card_date_format)
+                val dateFormatter = remember(pattern) {
+                    DateTimeFormatter.ofPattern(pattern)
+                }

267-268: 카테고리별 아이콘 매핑이 하드코딩되어 있습니다.

FIXME 주석이 있으나, PlaceCategory에 대한 toDisplayRes()처럼 아이콘 매핑 유틸도 함께 구현하면 좋겠습니다. 이 작업을 별도 이슈로 트래킹하시겠습니까?

feature/home/src/main/java/com/yapp/ndgl/feature/home/main/RecommendedContentSection.kt (1)

130-134: home_popular_travel_nights_days 리소스 이름 확인 필요.

추천 콘텐츠 섹션에서 R.string.home_popular_travel_nights_days를 사용하고 있는데, 리소스 이름에 "popular"가 포함되어 있어 의미적으로 맞지 않습니다. PopularTravelSection에서도 동일한 리소스를 사용하므로 공용 리소스명(예: home_common_nights_days)으로 변경하면 가독성이 향상됩니다.

data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/TravelTemplateRepository.kt (1)

10-25: Repository에 인터페이스 추상화 부재.

TravelTemplateRepository가 구체 클래스로 직접 주입되고 있어 테스트 시 모킹이 어렵습니다. 인터페이스를 분리하고 Hilt @Binds로 바인딩하면 테스트 용이성과 의존성 역전 원칙 준수에 도움이 됩니다. 다른 repository(TravelProgramRepository, UserTravelRepository)에도 동일하게 적용할 수 있습니다.

feature/home/src/main/java/com/yapp/ndgl/feature/home/main/HomeScreen.kt (1)

65-99: LazyColumn의 조건부 item 렌더링 패턴이 일관되지 않습니다.

Line 81의 PopularTravelSectionitem { } 내부에서 if 체크를 하고 있어 조건이 false일 때도 빈 아이템이 생성됩니다. 반면 Line 91의 RecommendedContentSectionifitem { } 블록 자체를 감싸서 조건이 false이면 아이템이 생성되지 않습니다.

PopularTravelSection도 동일한 패턴으로 통일하는 것을 권장합니다.

♻️ 제안하는 수정
-            item {
-                if (state.filteredPopularTravels.isNotEmpty()) {
-                    PopularTravelSection(
-                        tabs = state.travelProgramTabs,
-                        selectedTabIndex = state.popularTravelSelectedTabIndex,
-                        travels = state.filteredPopularTravels,
-                        onTabSelected = onTabSelected,
-                    )
-                }
-            }
+            if (state.filteredPopularTravels.isNotEmpty()) {
+                item {
+                    PopularTravelSection(
+                        tabs = state.travelProgramTabs,
+                        selectedTabIndex = state.popularTravelSelectedTabIndex,
+                        travels = state.filteredPopularTravels,
+                        onTabSelected = onTabSelected,
+                    )
+                }
+            }
feature/home/src/main/java/com/yapp/ndgl/feature/home/main/HomeViewModel.kt (4)

39-41: 세션 초기화 실패 시 사용자에게 빈 화면이 영구적으로 노출됩니다.

initSession 실패 시 loadHomeContents()가 호출되지 않아 UI가 초기 상태(빈 화면 또는 로딩 상태)에 머무릅니다. FIXME가 이미 남겨져 있지만, 최소한 에러 상태로 전환하거나 재시도 로직을 추가하는 것을 권장합니다.

이 부분에 대한 에러 상태 처리 구현을 도와드릴까요? 또는 별도 이슈로 트래킹하시겠습니까?


95-97: loadMyTravel 실패 시 상태가 갱신되지 않습니다.

onFailure에서 로깅만 하고 상태를 업데이트하지 않아, myTravel이 초기 상태에 머무릅니다. HomeStatemyTravel 초기값이 로딩 상태라면 사용자에게 무한 로딩으로 보일 수 있습니다. 실패 시에도 MyTravel.None 등으로 상태를 전환하는 것이 좋습니다.

제안
                 .onFailure {
                     Timber.e("Failed to load upcoming travel: $it")
+                    reduce { copy(myTravel = HomeState.MyTravel.None) }
                 }

101-110: 에러 로깅에 Timber.d(디버그) 대신 Timber.e(에러)를 사용해야 합니다.

loadMyTravel에서는 Timber.e를 사용하는 반면, 이 메서드와 loadPopularTemplatesByPrograms 내부(Line 107, 116, 123)에서는 Timber.d를 사용하고 있습니다. API 호출 실패는 에러 레벨로 로깅해야 프로덕션에서 문제를 추적할 수 있습니다.

제안
-                Timber.d("fail to load popular $it")
+                Timber.e("fail to load popular $it")

112-161: loadPopularTemplatesByPrograms에서 불필요한 viewModelScope.launch 사용.

이 함수는 이미 loadPopularTemplates()viewModelScope.launch 내부에서 호출됩니다. 내부에서 다시 viewModelScope.launch를 사용하면 호출자와 독립적인 코루틴이 생성되어, 호출자 코루틴이 완료되어도 내부 작업이 아직 진행 중일 수 있습니다. coroutineScope { } 사용 또는 suspend fun으로 변경하면 구조화된 동시성(structured concurrency)을 유지할 수 있습니다.

제안
-    private fun loadPopularTemplatesByPrograms(programs: List<TravelProgram>) {
-        viewModelScope.launch {
+    private suspend fun loadPopularTemplatesByPrograms(programs: List<TravelProgram>) {
+        coroutineScope {
             val allTemplateDeferred = async {

Comment thread lint.xml Outdated
@jihee-dev jihee-dev force-pushed the feature/NDGL-88/impl-home-api branch from 0aee3aa to ac20eec Compare February 16, 2026 00:59
@jihee-dev jihee-dev force-pushed the feature/NDGL-88/impl-home-api branch 2 times, most recently from decc74a to b5a6455 Compare February 16, 2026 01:08
@jihee-dev jihee-dev force-pushed the feature/NDGL-88/impl-home-api branch from b5a6455 to 5f21cb0 Compare February 16, 2026 01:10
@jihee-dev jihee-dev merged commit f5c1e76 into develop Feb 16, 2026
2 checks passed
@jihee-dev jihee-dev deleted the feature/NDGL-88/impl-home-api branch February 16, 2026 01:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant