Skip to content

[NDGL-112] 장소 즐겨찾기 관련 API 연동#35

Merged
mj010504 merged 1 commit into
developfrom
feature/NDGL-112
Feb 24, 2026
Merged

[NDGL-112] 장소 즐겨찾기 관련 API 연동#35
mj010504 merged 1 commit into
developfrom
feature/NDGL-112

Conversation

@mj010504
Copy link
Copy Markdown
Contributor

@mj010504 mj010504 commented Feb 24, 2026

개요

  • 장소 즐겨찾기 관련 API 연동

디자인

변경사항

  • 장소 즐겨찾기 추가 API 연동
  • 장소 즐겨찾기 삭제 API 연동
  • 즐겨찾기한 장소 불러오기 API 연동(현재 Paging 미적용)

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 여행지 북마크 저장 및 해제 기능 추가
    • 저장한 여행지 목록 조회 기능 추가
    • 여행지 추천 목록 동적 로딩 개선
  • 개선 사항

    • 여행지 추가 화면의 추천 및 북마크된 장소 데이터 로딩 최적화

@mj010504 mj010504 self-assigned this Feb 24, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 24, 2026

Walkthrough

여행 앱에 장소 북마크 기능을 추가하는 변경사항입니다. 새로운 API 엔드포인트 세 개를 정의하고, 대응하는 데이터 모델과 저장소 메서드를 추가했습니다. UI 계층에서는 추천 및 북마크된 장소를 동적으로 로드하고 북마크 상태를 관리하도록 업데이트했습니다.

Changes

Cohort / File(s) Summary
API 엔드포인트 및 데이터 모델
data/travel/src/main/java/com/yapp/ndgl/data/travel/api/PlaceApi.kt, data/travel/src/main/java/com/yapp/ndgl/data/travel/model/GetBookmarkedPlacesResponse.kt
북마크된 장소 조회(GET), 북마크 추가(POST), 삭제(DELETE)를 위한 3개의 Retrofit 엔드포인트와 응답 데이터 모델(GetBookmarkedPlacesResponse, PlaceInfo) 추가
저장소 계층
data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/PlaceRepository.kt
API 엔드포인트에 위임하는 3개의 공개 suspend 메서드(getBookmarkedPlaces, bookmarkPlace, unBookmarkPlace) 추가
데이터 계약 및 UI 컴포넌트
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/AddItineraryContract.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/component/AddItineraryBottomSheet.kt
SelectablePlace의 thumbnail 필드를 nullable로 변경, AddItineraryBottomSheet에 places 파라미터 추가
뷰모델 및 UI 로직
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/AddItineraryViewModel.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/AddItineraryScreen.kt
하드코딩된 테스트 데이터를 동적 로딩(추천 장소, 북마크된 장소)으로 교체, 병렬 코루틴 기반 데이터 페칭 추가, 북마크 토글 기능 구현, 프리뷰 데이터 인라인화

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 즐겨찾기 관련 API 연동이라는 주요 변경사항을 명확하게 요약하고 있으며, 변경 내용과 일치합니다.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/NDGL-112

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: 2

🧹 Nitpick comments (3)
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/AddItineraryContract.kt (1)

57-62: thumbnail 기본값 추가 고려

nullable로 바뀐 건 좋습니다. 선택적 필드라면 기본값을 null로 두면 호출부에서 불필요한 전달을 줄일 수 있습니다.

♻️ 제안 변경
 data class SelectablePlace(
     val googlePlaceId: String,
     val name: String,
     val placeType: PlaceType = PlaceType.ATTRACTION,
-    val thumbnail: String?,
+    val thumbnail: String? = null,
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/AddItineraryContract.kt`
around lines 57 - 62, SelectablePlace의 thumbnail이 nullable로 변경된 것은 적절하나 기본값을
지정하지 않아 호출부에서 불필요하게 전달해야 할 수 있으므로 thumbnail의 기본값을 null로 설정해 호출 시 생략 가능하도록 변경하세요;
데이터 클래스 SelectablePlace의 프로퍼티 thumbnail에 기본값 null을 부여하고(생성자 시 기본 파라미터) 관련 생성
호출부가 문제 없이 컴파일되는지(필요시 호출부에서 인자 제거)만 확인하면 됩니다.
data/travel/src/main/java/com/yapp/ndgl/data/travel/api/PlaceApi.kt (1)

33-33: 사소한 포맷팅: @Query("size") 뒤에 공백 누락

다른 @Query 어노테이션들과 일관성을 위해 공백을 추가해주세요.

💅 수정 제안
-        `@Query`("size")size: Int? = null,
+        `@Query`("size") size: Int? = null,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@data/travel/src/main/java/com/yapp/ndgl/data/travel/api/PlaceApi.kt` at line
33, In PlaceApi.kt update the parameter declaration `@Query("size")size: Int? =
null` to match other parameters by inserting a space after the annotation so it
reads `@Query("size") size: Int? = null`; this is a purely formatting change to
keep `@Query` annotations consistent across the interface.
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/AddItineraryViewModel.kt (1)

90-108: Timber.d로 에러를 로깅하고 있지만, 에러 수준이 적절하지 않을 수 있습니다.

API 호출 실패는 Timber.d (debug) 보다 Timber.e (error) 또는 Timber.w (warning)가 더 적합합니다. 디버그 레벨은 프로덕션 빌드에서 필터링될 수 있습니다.

♻️ 수정 제안
         }.onFailure {
             // FIXME: Handle Error
-            Timber.d("${it.message} $it")
+            Timber.e(it, "Failed to load bookmarked places")
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/AddItineraryViewModel.kt`
around lines 90 - 108, The onFailure block in loadBookmarkedPlaces currently
logs failures with Timber.d which is too low-severity for API call errors;
change the logging to Timber.e (or Timber.w if you prefer warning-level) and
pass the throwable so the stacktrace is recorded (e.g., replace the
Timber.d("${it.message} $it") in the onFailure of loadBookmarkedPlaces with a
Timber.e call that includes the throwable and a short context message).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/AddItineraryViewModel.kt`:
- Around line 308-339: When placeRepository.getPlace(checkedPlaceId) returns
null in AddItineraryViewModel (the checkedPlaceId branch), don’t silently
navigate back; instead emit an error side effect before returning. Update the
branch that checks if (placeDetail != null) so the else path calls
postSideEffect with an error/feedback action (e.g.,
AddItinerarySideEffect.ShowError or ShowToast) describing the failure, and only
navigate back after user-visible feedback if appropriate; reference the existing
postSideEffect and AddItinerarySideEffect types and ensure
userTravelRepository.emitAddPlaceEvent is only called when placeDetail is
non-null.
- Around line 357-383: bookmarkPlace calls placeRepository using the passed
placeId but updates UI state using state.value.selectedPlaceDetail, which can
mismatch; ensure consistency by checking
selectedPlaceDetail?.placeInfo?.googlePlaceId against the placeId before
mutating state: call placeRepository.bookmarkPlace/unBookmarkPlace with the
given placeId as you do, but only run the reduce { copy(selectedPlaceDetail =
...) } branch when selectedPlaceDetail?.placeInfo?.googlePlaceId == placeId
(otherwise skip the local selectedPlaceDetail update); use the existing symbols
bookmarkPlace, selectedPlaceDetail, placeInfo.googlePlaceId,
placeRepository.bookmarkPlace/unBookmarkPlace and still call
loadBookmarkedPlaces() after success.

---

Nitpick comments:
In `@data/travel/src/main/java/com/yapp/ndgl/data/travel/api/PlaceApi.kt`:
- Line 33: In PlaceApi.kt update the parameter declaration `@Query("size")size:
Int? = null` to match other parameters by inserting a space after the annotation
so it reads `@Query("size") size: Int? = null`; this is a purely formatting
change to keep `@Query` annotations consistent across the interface.

In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/AddItineraryContract.kt`:
- Around line 57-62: SelectablePlace의 thumbnail이 nullable로 변경된 것은 적절하나 기본값을 지정하지
않아 호출부에서 불필요하게 전달해야 할 수 있으므로 thumbnail의 기본값을 null로 설정해 호출 시 생략 가능하도록 변경하세요; 데이터
클래스 SelectablePlace의 프로퍼티 thumbnail에 기본값 null을 부여하고(생성자 시 기본 파라미터) 관련 생성 호출부가 문제
없이 컴파일되는지(필요시 호출부에서 인자 제거)만 확인하면 됩니다.

In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/AddItineraryViewModel.kt`:
- Around line 90-108: The onFailure block in loadBookmarkedPlaces currently logs
failures with Timber.d which is too low-severity for API call errors; change the
logging to Timber.e (or Timber.w if you prefer warning-level) and pass the
throwable so the stacktrace is recorded (e.g., replace the
Timber.d("${it.message} $it") in the onFailure of loadBookmarkedPlaces with a
Timber.e call that includes the throwable and a short context message).

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2af1983 and 845db06.

📒 Files selected for processing (7)
  • data/travel/src/main/java/com/yapp/ndgl/data/travel/api/PlaceApi.kt
  • data/travel/src/main/java/com/yapp/ndgl/data/travel/model/GetBookmarkedPlacesResponse.kt
  • data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/PlaceRepository.kt
  • feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/AddItineraryContract.kt
  • feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/AddItineraryScreen.kt
  • feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/AddItineraryViewModel.kt
  • feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/component/AddItineraryBottomSheet.kt

Comment on lines +308 to +339
// AddItineraryBottomSheet에서 체크박스로 선택한 장소가 있는 경우
if (checkedPlaceId != null) {
// 장소 상세 정보 가져오기
val placeDetail = suspendRunCatching {
placeRepository.getPlace(checkedPlaceId)
}.getOrNull()

if (placeDetail != null) {
userTravelRepository.emitAddPlaceEvent(
AddPlaceEvent(
travelId = travelId,
day = day,
googlePlaceId = placeDetail.place.id,
name = placeDetail.place.name,
latitude = placeDetail.place.location.latitude,
longitude = placeDetail.place.location.longitude,
thumbnail = placeDetail.place.thumbnail,
placeType = placeDetail.place.category,
address = placeDetail.place.formattedAddress,
phoneNumber = placeDetail.place.nationalPhoneNumber
?: placeDetail.place.internationalPhoneNumber,
googleMapsUri = placeDetail.place.googleMapsUri,
websiteUrl = placeDetail.place.websiteUri,
rating = placeDetail.place.rating,
userRatingCount = placeDetail.place.userRatingCount,
estimatedDuration = 60, // 기본값 60분
),
)
postSideEffect(AddItinerarySideEffect.NavigateBack)
return@launch
}
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Feb 24, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

checkedPlaceId 경로에서 장소 상세 조회 실패 시 사용자 피드백 없이 뒤로 이동

placeRepository.getPlace(checkedPlaceId)가 실패하면 (getOrNull()null), 사용자가 장소를 체크하고 추가 버튼을 눌렀음에도 아무런 피드백 없이 뒤로 이동합니다. 최소한 에러 사이드이펙트를 발생시키거나 토스트 메시지를 보여주는 것이 좋겠습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/additinerary/AddItineraryViewModel.kt`
around lines 308 - 339, When placeRepository.getPlace(checkedPlaceId) returns
null in AddItineraryViewModel (the checkedPlaceId branch), don’t silently
navigate back; instead emit an error side effect before returning. Update the
branch that checks if (placeDetail != null) so the else path calls
postSideEffect with an error/feedback action (e.g.,
AddItinerarySideEffect.ShowError or ShowToast) describing the failure, and only
navigate back after user-visible feedback if appropriate; reference the existing
postSideEffect and AddItinerarySideEffect types and ensure
userTravelRepository.emitAddPlaceEvent is only called when placeDetail is
non-null.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

추후 수정

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@mj010504, 알겠습니다! 나중에 수정하시는군요.

이 내용을 추적하기 위해 GitHub 이슈를 생성해드릴까요? 생성 시 PR과 댓글 링크를 포함하여 나중에 참고하기 쉽게 해드리겠습니다.


🧠 Learnings used
Learnt from: mj010504
Repo: YAPP-Github/27th-App-Team-1-Android PR: 26
File: feature/travel/src/main/java/com/yapp/ndgl/feature/travel/addplace/AddPlaceViewModel.kt:71-90
Timestamp: 2026-02-18T12:08:18.209Z
Learning: In the travel feature (place photo loading logic in AddPlaceViewModel.kt and AddItineraryViewModel.kt), retain a deliberate 1-second delay before each photo fetch when loading data for newly searched places. This accounts for backend latency needed to save place data before photos are retrievable. Ensure the delay is configurable (e.g., via feature flag or constant) and documented, so it can be adjusted or removed if backend performance improves. This guidance applies to all travel feature view models handling similar photo fetch flows.

Learnt from: mj010504
Repo: YAPP-Github/27th-App-Team-1-Android PR: 17
File: feature/travel/src/main/java/com/yapp/ndgl/feature/travel/traveldetail/component/TimelineContent.kt:243-251
Timestamp: 2026-02-11T12:08:02.377Z
Learning: In feature/travel/src/main/java/com/yapp/ndgl/feature/travel/traveldetail/component/TimelineContent.kt, the startIndex calculation for the WheelPicker intentionally does NOT subtract visibleItemCount/2 offset. This design choice preserves showing 5 items initially and creates an offset from the initialIndex parameter. Reviewers should not request centering the active item unless the UI design is changed; keep this behavior as the intended UX for this component.

Learnt from: jihee-dev
Repo: YAPP-Github/27th-App-Team-1-Android PR: 25
File: feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelContract.kt:52-54
Timestamp: 2026-02-17T22:21:31.141Z
Learning: Use the naming convention estimatedDuration for duration-related fields across Kotlin files to align with server API response parameter names and maintain consistency throughout the codebase. When introducing or renaming duration fields, replace generic names (e.g., duration, totalDuration) with estimatedDuration and update all references (data classes, mappings, JSON parsing) accordingly to avoid mismatch with server payloads.

Learnt from: jihee-dev
Repo: YAPP-Github/27th-App-Team-1-Android PR: 27
File: feature/home/src/main/java/com/yapp/ndgl/feature/home/search/TemplateSearchScreen.kt:0-0
Timestamp: 2026-02-18T18:16:40.700Z
Learning: Use the correct Hilt Compose ViewModel import across Kotlin Android files: import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel (from androidx.hilt:hilt-lifecycle-viewmodel-compose:1.3.0). The old import androidx.hilt.navigation.compose.hiltViewModel is deprecated in Hilt 1.3.0. Ensure the hilt-lifecycle-viewmodel-compose dependency is added to Gradle (both Gradle catalog and module dependencies) and update imports accordingly to avoid runtime issues or build failures.

@mj010504 mj010504 merged commit 81bbc6f into develop Feb 24, 2026
4 checks passed
@mj010504 mj010504 deleted the feature/NDGL-112 branch February 24, 2026 13:02
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