Skip to content

Comments

[Feat] 홈 추가화면 API 연동#101

Merged
ikseong00 merged 27 commits intodevelopfrom
feat-home-extra-api
Oct 29, 2025
Merged

[Feat] 홈 추가화면 API 연동#101
ikseong00 merged 27 commits intodevelopfrom
feat-home-extra-api

Conversation

@t1nm1ksun
Copy link
Collaborator

@t1nm1ksun t1nm1ksun commented Oct 21, 2025

Related issue 🛠

Work Description 📝

  • 홈 추가화면에 필요한 api 들을 추가했습니다
  • 보호소 화면에 네이버 지도를 추가했습니다
  • 행정구역 드롭다운을 추가했습니다

Screenshot 📸

Uncompleted Tasks 😅

  • Task1

To Reviewers 📢

디바이스 아이디 설정 시점을 스플래시로 수정했으니 아직도 빈값이 들어가는지 확인 부탁드려요!
제 환경에서는 정상적으로 들어가고 있습니당

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 자원봉사 활동, 보호센터, 부서 정보 조회 기능 추가
    • 시/도·시/군/구 필터링 및 드롭다운 선택 UI 추가
    • 보호센터 지도 기반 표시, 현재 위치 검색 및 위치 권한 워크플로우 추가
  • 스타일

    • Material3 UI 컴포넌트로 마이그레이션 및 텍스트 오버플로우 처리 개선
  • 기타

    • 리소스 문자열 및 아이콘 추가, 기기 식별 처리 개선

…extra-api

# Conflicts:
#	app/src/main/java/com/example/findu/presentation/ui/base/TopAppBar.kt
…tra-api

# Conflicts:
#	app/src/main/java/com/example/findu/data/mapper/todomain/home/HomeResponseDtoMapper.kt
# Conflicts:
#	app/src/main/java/com/example/findu/data/dataremote/util/Constraints.kt
#	app/src/main/java/com/example/findu/di/RepositoryModule.kt
#	app/src/main/java/com/example/findu/di/UseCaseModule.kt
#	app/src/main/java/com/example/findu/domain/model/extra/HomeExtraDataType.kt
#	app/src/main/java/com/example/findu/presentation/ui/extra/viewmodel/HomeExtraViewModel.kt
#	app/src/main/java/com/example/findu/presentation/ui/home/viewmodel/HomeViewModel.kt
#	app/src/main/java/com/example/findu/presentation/ui/login/viewmodel/LoginViewModel.kt
# Conflicts:
#	app/src/main/java/com/example/findu/data/mapper/todomain/SearchResponseDtoMapper.kt
#	app/src/main/java/com/example/findu/di/UseCaseModule.kt
@t1nm1ksun t1nm1ksun self-assigned this Oct 21, 2025
@t1nm1ksun t1nm1ksun added the feat label Oct 21, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 21, 2025

Walkthrough

홈 추가화면(자원봉사자·보호센터·부서·행정구역) 관련 API·DTO·서비스·리포지토리·유스케이스·매퍼와 DI 바인딩을 추가하고, Compose UI(지도·드롭다운·material3) 및 HomeExtraViewModel의 상태/이벤트 로직을 확장했습니다.

Changes

Cohort / File(s) Change summary
원격 API / 데이터소스
app/src/main/java/com/example/findu/data/dataremote/service/InformationService.kt, app/src/main/java/com/example/findu/data/dataremote/datasource/InformationRemoteDataSource.kt, app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/InformationRemoteDataSourceImpl.kt, app/src/main/java/com/example/findu/data/dataremote/datasource/HomeRemoteDataSource.kt, app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/HomeRemoteDataSourceImpl.kt
신규 InformationService/InformationRemoteDataSource(+Impl) 추가 및 HomeResponseDto import 경로(home 패키지로) 업데이트.
응답 DTO (information)
app/src/main/java/com/example/findu/data/dataremote/model/response/information/* (CentersResponseDto.kt, DepartmentsResponseDto.kt, SidoListDto.kt, SigunguListDto.kt, VolunteersResponseDto.kt)
신규 serializable DTO들 추가(센터, 부서, 시도, 시군구, 자원봉사).
응답 DTO (home)
app/src/main/java/com/example/findu/data/dataremote/model/response/home/HomeResponseDto.kt
HomeResponseDto 패키지 이동: .response.response.home.
도메인: 리포지토리/모델/페이징
app/src/main/java/com/example/findu/domain/repository/InformationRepository.kt, app/src/main/java/com/example/findu/domain/model/extra/HomeExtraDataType.kt, app/src/main/java/com/example/findu/data/repositoryimpl/InformationRepositoryImpl.kt
InformationRepository 인터페이스 추가, InformationRepositoryImpl 구현 추가, Center에 위경도 필드 및 PagedResult·Sido 도메인 타입 추가.
매퍼
app/src/main/java/com/example/findu/data/mapper/todomain/extra/InformationResponseDtoMapper.kt, .../SidoListDtoMapper.kt, .../home/HomeResponseDtoMapper.kt
DTO→도메인 매퍼 추가(정보 관련) 및 홈 매퍼 import 패스 업데이트.
유스케이스
app/src/main/java/com/example/findu/domain/usecase/extra/* (GetVolunteers/ GetCenters/ GetDepartments/ GetSido/ GetSigungu), app/src/main/java/com/example/findu/domain/usecase/home/GetHomeUseCase.kt, .../auth/*
정보용 유스케이스들 추가 및 일부 usecase 파일 패키지(auth/home) 재배치.
DI 모듈
app/src/main/java/com/example/findu/di/{DataSourceModule.kt,RepositoryModule.kt,ServiceModule.kt,UseCaseModule.kt}
InformationService 제공자 추가, InformationRemoteDataSource 및 InformationRepository 바인딩 추가, 정보 유스케이스 제공자 추가.
Presentation: HomeExtra UI & ViewModel
app/src/main/java/com/example/findu/presentation/ui/extra/{HomeExtraFragment.kt,view/*,component/*,viewmodel/HomeExtraViewModel.kt}
HomeExtraFragment에 위치 권한/위치 획득 로직 추가, Extra 화면들(Volunteer/Department/Center) 시그니처 확장, ExtraCenterScreen → 지도(NaverMap) 기반 UI로 대체, ExtraDistrictItem 컴포저블 추가, HomeExtraViewModel 상태/이벤트·데이터 로딩(유스케이스 연동) 대폭 추가·리팩터링.
Presentation: 기타 변경
presentation/ui/base/TopAppBar.kt, .../component/Extra{Center,Department,Volunteer}Item.kt, presentation/ui/home/viewmodel/HomeViewModel.kt, .../login/viewmodel/LoginViewModel.kt, .../onboarding/viewmodel/OnboardingViewModel.kt, MainActivity.kt, SplashActivity.kt
material → material3 마이그레이션(일부 컴포저블), 일부 usecase import 패스 변경, SetDeviceIdUseCase 주입 이동(MainActivity 제거 → SplashActivity 추가).
리소스 & 문자열
app/src/main/res/drawable/ic_extra_district_dropdown_24.xml, ic_place_now_24.xml, app/src/main/res/values/strings.xml
신규 드로어블 2개 추가 및 시/도·시군구 선택 문자열 리소스 추가.
유틸
app/src/main/java/com/example/findu/data/dataremote/util/Constraints.kt
ApiConstraints에 INFORMATION = "informations" 상수 추가.

Sequence Diagram(s)

sequenceDiagram
    participant FR as HomeExtraFragment
    participant VM as HomeExtraViewModel
    participant UC as Get*UseCase
    participant REPO as InformationRepository
    participant RDS as InformationRemoteDataSource
    participant SVC as InformationService
    participant API as Remote API

    FR->>VM: 사용자 액션 / 시작 이벤트
    VM->>UC: invoke(params)
    UC->>REPO: get*(params)
    REPO->>RDS: get*(params)
    RDS->>SVC: Retrofit call
    SVC->>API: HTTP GET
    API-->>SVC: BaseResponse<DTO>
    SVC-->>RDS: BaseResponse<DTO>
    RDS-->>REPO: BaseResponse<DTO>
    REPO->>REPO: handleBaseResponse() / toDomain()
    REPO-->>UC: Result<PagedResult/ List>
    UC-->>VM: Result
    VM->>VM: 상태 업데이트 (sido/sigungu/center 위치 등)
    VM-->>FR: HomeExtraUiState
    FR->>FR: UI 렌더(지도/드롭다운/리스트)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

(다계층 변경: API→데이터소스→리포지토리→유스케이스→ViewModel→UI(지도, 권한, 상태) 및 DI/매퍼/DTO 추가로 통합 검토 필요)

Possibly related PRs

Suggested reviewers

  • ikseong00
  • nasohee

Poem

🧭 지도 위에 새 핀을 찍고, API가 속삭인다.
DTO는 옷을 갈아입어 도메인이 되고,
뷰모델은 바람을 읽어 위치를 옮기네.
드롭다운을 열면 시도가 손을 흔들고,
작은 아이콘 하나가 길을 비춘다. ✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning 이슈 #94 범위를 벗어난 변경사항들이 발견됩니다. SetDeviceIdUseCase를 MainActivity에서 제거하고 SplashActivity로 이동한 변경, 여러 use case를 auth/home 패키지로 리팩토링한 변경, Material 컴포넌트를 Material3로 업그레이드한 변경, SetNicknameUseCase에서 어노테이션을 제거한 변경이 있습니다. 특히 PR 설명에서 작성자가 명시한 "디바이스 아이디 설정 시점을 스플래시로 수정"한 부분은 이슈 범위와 무관한 별도 작업으로 보입니다. 범위를 벗어난 변경사항들을 별도의 PR로 분리하는 것을 권장합니다. 이슈 #94는 "홈 추가화면 API 연동"에만 집중하고, 패키지 리팩토링, Material 업그레이드, 디바이스 ID 로직 변경 등은 각각 별개의 이슈와 PR로 관리하면 코드 리뷰 관점에서 더 명확하고 관리하기 수월할 것입니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed 제목 "[Feat] 홈 추가화면 API 연동"은 PR의 주요 목표를 명확하고 간결하게 나타냅니다. raw_summary에서 볼 수 있듯이 InformationRemoteDataSource, 여러 DTO, use case 추가, UI 화면 업데이트 등 모든 변경사항이 홈 추가화면의 API 연동을 중심으로 이루어져 있습니다. 제목은 이러한 핵심 목표를 정확하게 반영하고 있습니다.
Linked Issues Check ✅ Passed 이슈 #94의 목표 "홈 추가화면에 필요한 API들을 연동한다"가 충족되었습니다. PR에서는 InformationRemoteDataSource, InformationService, InformationRepository 등의 API 계층을 추가했고, 관련 DTO들을 정의했으며, 5개의 정보 조회 use case를 구현했습니다. HomeExtraViewModel과 UI 화면들도 이 API들을 연동하도록 업데이트되었으므로 이슈의 주요 요구사항이 충족됩니다.
Description Check ✅ Passed PR 설명은 제공된 템플릿의 모든 필수 섹션을 포함하고 있습니다. Related issue (#94), Work Description (3가지 주요 작업 항목), Screenshot (img 태그 포함), Uncompleted Tasks, To Reviewers 섹션이 모두 작성되어 있습니다. 작업 내용이 구체적으로 설명되어 있으며 리뷰어를 위한 추가 정보도 제공되고 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-home-extra-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

@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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/src/main/java/com/example/findu/domain/usecase/auth/PostSignupUseCase.kt (1)

12-25: 디바이스 아이디 검증 필수 - 빈 문자열 전송 위험

검증 결과 실제 문제가 확인되었습니다. DeviceLocalDataSourceImpl.kt에서 INITIAL_VALUE = "" (빈 문자열)로 정의되어 있으며, Splash에서 setDeviceId()를 호출하기 전에 signup 플로우가 실행될 경우 getDeviceId()는 빈 문자열을 반환합니다.

현재 PostSignupUseCase line 24의 userInfoRepository.getDeviceId()는 검증 없이 API에 전달되므로, 초기화되지 않은 빈 deviceId가 백엔드로 전송될 수 있습니다.

필수 조치:

  • PostSignupUseCase에서 deviceId 빈 문자열/null 검증 추가
  • 또는 Splash 초기화 완료 전까지 signup 플로우 차단 보장
app/src/main/java/com/example/findu/domain/usecase/SetNicknameUseCase.kt (1)

1-11: 불필요한 import 제거 필요 - 현재 코드의 @Inject, @singleton 어노테이션은 제거되었으나 import는 미정리 상태입니다

현재 상황:

  • SetNicknameUseCase는 이미 UseCaseModuleprovideSetNicknameUseCase() 함수(lines 267-269)에서 @Provides @Singleton으로 DI 그래프에 제공되고 있습니다
  • 클래스 정의에서 어노테이션을 제거하신 것이 맞는데, lines 4-5의 import 문이 정말로 사용되지 않고 있습니다
 package com.example.findu.domain.usecase
 
 import com.example.findu.domain.repository.UserInfoRepository
-import javax.inject.Inject
-import javax.inject.Singleton
 
 class SetNicknameUseCase(
     private val userInfoRepository: UserInfoRepository
 ) {
     operator fun invoke(nickname: String) = userInfoRepository.setNickname(nickname = nickname)
 }

DI 구성은 이미 제대로 되어 있으므로, 사용하지 않는 import만 정리하시면 됩니다.

🧹 Nitpick comments (32)
app/src/main/res/drawable/ic_extra_district_dropdown_24.xml (1)

1-13: 드롭다운 아이콘 구현이 깔끔합니다.

Android 명명 규칙도 잘 따르고, 24x24 표준 크기에 투명한 배경과 스트로크만 사용한 깔끔한 설계네요. 행정구역 선택 UI에 잘 어울릴 것 같습니다.

한 가지, Line 7의 12.001 값을 12로 단순화하면 더 읽기 쉬울 수 있습니다. 설계 도구에서 생긴 반올림 아티팩트로 보이는데, 기능상 차이는 없으니 선택사항입니다.

- android:pathData="M7,10L12.001,14.58L17,10"
+ android:pathData="M7,10L12,14.58L17,10"
app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraVolunteerItem.kt (2)

72-80: 주소 텍스트 정렬 방식 재검토 필요.

textAlign = TextAlign.Endoverflow = TextOverflow.Ellipsis를 함께 사용하면 긴 주소가 "...동물보호센터" 처럼 오른쪽 정렬된 상태로 잘릴 수 있어서 UX가 다소 어색할 수 있습니다.

주소는 보통 앞부분(시/구 정보)이 더 중요하므로, textAlign을 제거하거나 다른 레이아웃 방식을 고려해보세요.

다음과 같이 수정을 고려해보세요:

 Text(
     text = volunteerWork.address,
     style = FindUTheme.typography.body2SemiBold14,
     color = FindUTheme.colors.gray5,
-    textAlign = TextAlign.End,
     maxLines = 1,
     overflow = TextOverflow.Ellipsis,
     modifier = Modifier.weight(1f)
 )

61-65: 다른 텍스트 필드도 오버플로우 보호 고려해보세요.

모집 기간, 봉사 날짜, 봉사 시간 텍스트 필드들에도 maxLinesoverflow 속성을 추가하면 예상치 못한 긴 데이터로부터 UI를 보호할 수 있습니다.

날짜/시간 형식이라 실제로 문제가 될 가능성은 낮지만, 일관성과 방어적 코딩 측면에서 고려해볼 만합니다.

예시:

 Text(
     text = volunteerWork.recruitmentPeriod,
     style = FindUTheme.typography.body2SemiBold14,
-    color = FindUTheme.colors.gray5
+    color = FindUTheme.colors.gray5,
+    maxLines = 1,
+    overflow = TextOverflow.Ellipsis
 )

Also applies to: 88-92, 100-104

app/src/main/java/com/example/findu/presentation/ui/base/TopAppBar.kt (1)

8-9: Material3 전환 좋습니다 — i18n/접근성 소소 개선 제안

  • contentDescription 하드코딩("Navigation Icon"/"Action Icon") 대신 stringResource로 지역화 권장.
  • M3 컴포넌트 사용이 늘어난다면 CenterAlignedTopAppBar 등 M3 TopAppBar로의 통합도 고려해 보세요(동일 디자인이면 접근성/semantics 이득).

예)

contentDescription = stringResource(R.string.cd_navigation_icon)
contentDescription = stringResource(R.string.cd_action_icon)
app/src/main/java/com/example/findu/domain/usecase/home/GetHomeUseCase.kt (1)

9-12: 불필요한 map 제거 및 파라미터 포맷 정리

동일 반환이므로 map { it } 제거하고 가독성 향상을 위해 공백 정리하면 깔끔합니다.

-    suspend operator fun invoke(lat: Double?=null,lon:Double?=null): Result<HomeData> =
-        homeRepository.getHome(lat,lon).map { homeData ->
-            homeData
-        }
+    suspend operator fun invoke(
+        lat: Double? = null,
+        lon: Double? = null
+    ): Result<HomeData> = homeRepository.getHome(lat, lon)

추가로, lat/lon이 null일 때 레포지토리의 기본 동작(예: 디바이스 위치 미설정 시 처리)이 명확한지 확인해 주세요.

app/src/main/java/com/example/findu/data/mapper/todomain/LoginResponseDtoMapper.kt (1)

4-4: 확장함수 이름 충돌 위험 확인됨 — alias 사용을 권장합니다

검색 결과 toDomain 확장함수가 30개 이상 정의되어 있으며, 패키지별로 다양한 타입에 대해 정의되어 있습니다. 현재 라인 4의 import com.example.findu.data.mapper.todomain.home.toDomain은 home 패키지의 여러 toDomain 확장함수를 임포트하고 있는데, 라인 10의 userInfo?.toDomain()UserInfoDto 타입에 대한 확장함수를 호출합니다.

UserInfoDto.toDomain()UserInfoDtoMapper.kt에 정의되어 있으므로, 명확한 의도를 드러내기 위해 alias import를 사용하는 것이 좋습니다.

-import com.example.findu.data.mapper.todomain.home.toDomain
+import com.example.findu.data.mapper.todomain.home.toDomain as toHomeDomain
app/src/main/java/com/example/findu/domain/model/extra/HomeExtraDataType.kt (1)

25-28: 좌표를 값 객체로 래핑하면 확장성과 타입 안전성이 향상됩니다

현재 구조에서는 latitudelongitude를 개별 필드로 관리하고 있는데, 다음과 같이 값 객체로 묶으면 좋습니다:

data class Coordinates(val latitude: Double, val longitude: Double)

data class Center(
    val jurisdiction: List<String>,
    val centerName: String,
    val phoneNumber: String,
    val address: String,
    val location: Coordinates
) : HomeExtraDataType

이렇게 하면 좌표 전달·복사 시 오류 방지, 좌표 관련 로직 확장(좌표계 변환, 거리 계산 등)이 용이하며, 다른 도메인 모델에서도 재사용할 수 있습니다. 매퍼에서도 명확하게 관심사를 분리할 수 있습니다.

다만 PagedResultSido는 현재 구조가 적절하며, 이미 각 계층별로 적절히 분리되어 있습니다.

app/src/main/java/com/example/findu/presentation/ui/main/MainActivity.kt (1)

17-28: MainActivity에서 디바이스 아이디 처리 제거 확인했어요.

스플래시로 디바이스 아이디 설정을 옮기면서 여기선 제거하신 거 맞죠. MainActivity가 더 깔끔해졌네요!

혹시 빈 줄들(lines 17, 22-24)을 정리하시면 더 깔끔할 것 같아요:

 @AndroidEntryPoint
 class MainActivity : AppCompatActivity() {
     private lateinit var binding: ActivityMainBinding
-
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-
-
-
         binding = ActivityMainBinding.inflate(layoutInflater)
app/src/main/java/com/example/findu/domain/usecase/extra/GetVolunteersUseCase.kt (1)

8-9: lastId 기본값(Long.MAX_VALUE) 사용은 혼란/리스크가 큽니다. null과의 일관성 확인 필요

다른 유즈케이스(예: Centers/Departments)는 lastId = null 기본값을 사용합니다. 여기서만 Long.MAX_VALUE를 쓰면 페이지네이션 의미가 뒤틀리거나 첫 페이지에서 빈 결과가 날 수 있어요. 가능하면 기본값을 null로 통일하고, 호출부가 빈/초기 상태에서는 null을 전달하도록 맞추는 걸 권장합니다.

-    suspend operator fun invoke(lastId: Long? = Long.MAX_VALUE) =
+    suspend operator fun invoke(lastId: Long? = null) =
         repository.getVolunteers(lastId)

호출/레포지토리/서비스까지 null 의미(“첫 페이지”)가 유지되는지 한번 확인 부탁드립니다.

app/src/main/java/com/example/findu/domain/usecase/extra/GetDepartmentsUseCase.kt (1)

8-14: 빈 문자열 입력을 null로 정규화해 서버 필터링을 안정화하세요

UI에서 선택 해제 시 ""가 올 가능성이 있습니다. 이 값을 그대로 넘기면 쿼리 파라미터가 빈 문자열로 전달되어 의도치 않은 필터링이 될 수 있어요. 호출 직전에 blank를 null로 바꿔 주세요.

-    ) = repository.getDepartments(
-        district = district,
+    ) = repository.getDepartments(
+        district = district?.takeUnless { it.isBlank() },
         lastId = lastId
     )
app/src/main/java/com/example/findu/domain/usecase/extra/GetCentersUseCase.kt (1)

16-22: 매개변수 명칭 일관성(lon vs long)과 동시 필터링 정책 확인

  • 레포지토리 매개변수가 long이라 매핑이 살짝 혼동됩니다. 가능하면 전 구간에서 lng로 통일하는 게 가독성에 좋아요. 지금 단계에서는 주석으로 의도를 남기는 것도 방법입니다.
  • districtlat/lon을 동시에 넘길 때의 서버 동작 정책(AND/OR/우선순위)을 명확히 합의해주세요. 필요 시 require로 상호배타를 강제하거나 KDoc에 규칙을 남겨두는 걸 추천합니다.
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraDepartmentScreen.kt (2)

60-64: LazyColumn에 key를 지정해 리스트 안정성을 높이세요

아이템에 고유 키가 있다면 key를 지정해 재구성/스크롤 유지 품질을 개선해 주세요.

-        LazyColumn {
-            items(departments) {
+        LazyColumn {
+            items(departments, key = { it.id /* 없으면 다른 안정 식별자 */ }) {
                 ExtraDepartmentItem(department = it)
             }
         }

51-56: 선택 해제 시 빈 문자열 대신 null 전달 정책 정리 권장

selectedSigungu""일 수 있습니다. 하위 레이어(UseCase/Repository)에는 null을 “필터 없음”으로 사용하는 게 일반적이므로, 이벤트에서 빈 문자열을 null로 변환하거나 ViewModel에서 정규화해 주세요. (Departments 유즈케이스 코멘트와 동일 맥락)

app/src/main/java/com/example/findu/data/dataremote/model/response/information/SidoListDto.kt (1)

7-10: 필드 누락 방어를 위해 기본값을 두면 디코딩이 더 견고해집니다

서버가 일시적으로 sidoList를 누락/null로 보낼 경우를 대비해 기본값을 두는 걸 권장합니다. (선호에 따라 엄격 모드 유지도 OK)

 data class SidoListDto(
-    @SerialName("sidoList")
-    val sidoList: List<SidoItemDto>
+    @SerialName("sidoList")
+    val sidoList: List<SidoItemDto> = emptyList()
 )
app/src/main/java/com/example/findu/data/dataremote/model/response/information/CentersResponseDto.kt (1)

7-16: 역직렬화 견고성 확보: 기본값 추가 제안

백엔드 필드 누락/스키마 변경 시 크래시를 피하려면 기본값을 두는 게 안전합니다. 현재 전 필드가 non-null이라 미스매치에 취약합니다.

아래처럼 기본값을 권장합니다.

 @Serializable
 data class CentersResponseDto(
   @SerialName("centers")
-  val centers: List<CenterDto>,
+  val centers: List<CenterDto> = emptyList(),
   @SerialName("lastId")
-  val lastId: Long?,
+  val lastId: Long? = null,
   @SerialName("isLast")
-  val isLast: Boolean
+  val isLast: Boolean = false
 )
 
 @Serializable
 data class CenterDto(
   @SerialName("jurisdiction")
-  val jurisdiction: List<String>,
+  val jurisdiction: List<String> = emptyList(),
   @SerialName("centerName")
-  val centerName: String,
+  val centerName: String = "",
   @SerialName("phoneNumber")
-  val phoneNumber: String,
+  val phoneNumber: String = "",
-  @SerialName("address") val address: String,
-  @SerialName("latitude") val latitude: Double,
-  @SerialName("longitude") val longitude: Double
+  @SerialName("address") val address: String = "",
+  @SerialName("latitude") val latitude: Double = 0.0,
+  @SerialName("longitude") val longitude: Double = 0.0
 )

백엔드가 모든 필드를 항상 보장한다면 현 상태 유지도 가능하니, 실제 응답 샘플로 한 번만 확인 부탁드립니다.

Also applies to: 19-34

app/src/main/java/com/example/findu/data/mapper/todomain/extra/InformationResponseDtoMapper.kt (1)

12-26: 중복 제거로 간결화 제안

세 매퍼가 동일한 PagedResult 래핑을 반복합니다. 헬퍼로 공통화하면 가독성과 변경 용이성이 좋아집니다.

-fun VolunteersResponseDto.toDomain(): PagedResult<VolunteerWork> =
-    PagedResult(
-        items = volunteerWorks.map {
-            VolunteerWork(
-                institution = it.institution,
-                recruitmentPeriod = it.recruitmentPeriod,
-                address = it.address,
-                workPeriod = it.workPeriod,
-                workTime = it.workTime,
-                webLink = it.webLink
-            )
-        },
-        lastId = lastId,
-        isLast = isLast
-    )
+fun VolunteersResponseDto.toDomain(): PagedResult<VolunteerWork> =
+    mapToPagedResult(
+        volunteerWorks.map {
+            VolunteerWork(
+                institution = it.institution,
+                recruitmentPeriod = it.recruitmentPeriod,
+                address = it.address,
+                workPeriod = it.workPeriod,
+                workTime = it.workTime,
+                webLink = it.webLink
+            )
+        },
+        lastId,
+        isLast
+    )
@@
-fun CentersResponseDto.toDomain(): PagedResult<Center> =
-    PagedResult(
-        items = centers.map {
-            Center(
-                jurisdiction = it.jurisdiction,
-                centerName = it.centerName,
-                phoneNumber = it.phoneNumber,
-                address = it.address,
-                latitude = it.latitude,
-                longitude = it.longitude
-            )
-        },
-        lastId = lastId,
-        isLast = isLast
-    )
+fun CentersResponseDto.toDomain(): PagedResult<Center> =
+    mapToPagedResult(
+        centers.map {
+            Center(
+                jurisdiction = it.jurisdiction,
+                centerName = it.centerName,
+                phoneNumber = it.phoneNumber,
+                address = it.address,
+                latitude = it.latitude,
+                longitude = it.longitude
+            )
+        },
+        lastId,
+        isLast
+    )
@@
-fun DepartmentsResponseDto.toDomain(): PagedResult<Department> =
-    PagedResult(
-        items = departments.map {
-            Department(
-                name = it.departmentName,
-                district = it.district,
-                phone = it.phoneNumber
-            )
-        },
-        lastId = lastId,
-        isLast = isLast
-    )
+fun DepartmentsResponseDto.toDomain(): PagedResult<Department> =
+    mapToPagedResult(
+        departments.map {
+            Department(
+                name = it.departmentName,
+                district = it.district,
+                phone = it.phoneNumber
+            )
+        },
+        lastId,
+        isLast
+    )

추가 헬퍼(파일 하단 등)에 배치:

private inline fun <T> mapToPagedResult(
    items: List<T>,
    lastId: Long?,
    isLast: Boolean
): PagedResult<T> = PagedResult(items = items, lastId = lastId, isLast = isLast)
```<!-- As per coding guidelines -->


Also applies to: 28-42, 44-55

</blockquote></details>
<details>
<summary>app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt (2)</summary><blockquote>

`70-72`: **자원봉사 화면 호출 변경 LGTM**

popBackStack 전달 방식 자연스럽습니다. 성능 미세 최적화가 필요하면 `val popBack = remember { { findNavController().popBackStack() } }` 로 재생성 방지 가능하지만 현 상태도 충분합니다.

---

`114-114`: **빈 컨텐츠 상태 UI 제안**

`HomeExtraContent.None -> Unit` 대신 간단한 빈 화면(스켈레톤/플레이스홀더/재시도 버튼) 노출을 권장합니다. UX 측면에서 “아무 것도 안 보임”을 피할 수 있습니다.

</blockquote></details>
<details>
<summary>app/src/main/java/com/example/findu/data/repositoryimpl/InformationRepositoryImpl.kt (3)</summary><blockquote>

`27-38`: **파라미터 명확화: long → lng**

경도는 관례적으로 `lng`를 씁니다. `long`은 `Long`(타입)과 혼동 여지가 있어 변경을 권장합니다. 인터페이스/서비스까지 일괄 반영 필요.

```diff
-    override suspend fun getCenters(
-        lastId: Long?,
-        district: String?,
-        lat: Double?,
-        long: Double?,
-        size: Int?
-    ): Result<PagedResult<Center>> = runCatching {
-        remote.getCenters(lastId, district, lat, long, size)
+    override suspend fun getCenters(
+        lastId: Long?,
+        district: String?,
+        lat: Double?,
+        lng: Double?,
+        size: Int?
+    ): Result<PagedResult<Center>> = runCatching {
+        remote.getCenters(lastId, district, lat, lng, size)
             .handleBaseResponse()
             .getOrThrow()
             .toDomain()
     }

동일 리네임을 InformationRemoteDataSource/InformationService/호출부 전반에 적용해 주세요.


18-26: Result 체이닝 단순화

runCatching { ... getOrThrow() ... } 대신 handleBaseResponse().map { toDomain() } 체이닝이 더 간결합니다(예외 흐름 동일).

-): Result<PagedResult<VolunteerWork>> = runCatching {
-    remote.getVolunteers(lastId)
-        .handleBaseResponse()
-        .getOrThrow()
-        .toDomain()
-}
+): Result<PagedResult<VolunteerWork>> =
+    remote.getVolunteers(lastId)
+        .handleBaseResponse()
+        .map { it.toDomain() }

같은 패턴을 센터/부서/시도/시군구에도 적용 가능합니다.

Also applies to: 27-38, 40-48, 50-55, 57-62


50-62: 시도/시군구는 캐싱 고려

자주 변하지 않는 데이터입니다. 최초 1회 로드 후 메모리/로컬(예: Room/Datastore) 캐싱과 만료 정책(TTL) 적용을 추천합니다. API 부하와 체감 속도가 좋아집니다.

app/src/main/java/com/example/findu/data/dataremote/datasource/InformationRemoteDataSource.kt (1)

11-13: 첫 페이지 규약 정렬: lastId 기본값

페이징 첫 호출은 보통 lastId = null을 사용합니다. 현재 Long.MAX_VALUE 기본값은 혼란을 줄 수 있어 null 권장합니다(레포/서비스와 규약 일치 필요).

-    suspend fun getVolunteers(
-        lastId: Long? = Long.MAX_VALUE,
-    ): BaseResponse<VolunteersResponseDto>
+    suspend fun getVolunteers(
+        lastId: Long? = null,
+    ): BaseResponse<VolunteersResponseDto>

첫 페이지에서 서버가 null을 기대하는지 확인 부탁드립니다.

app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/InformationRemoteDataSourceImpl.kt (1)

21-28: 위경도 파라미터 네이밍 통일 제안(lon/lng vs long)

레이어마다 lon(VM/UseCase 추정)과 long(DS/Service)이 혼재되어 가독성이 떨어집니다. lng 또는 lon으로 통일을 권장합니다. 서비스/DS/리포지토리 인터페이스까지 일괄 변경하면 혼동을 줄일 수 있습니다.

app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (5)

77-90: 카메라 애니메이션 효과 중복으로 인한 점프 가능성

위치 변경(LaunchedEffect(latitude, longitude))과 센터 목록 변경(LaunchedEffect(centers))이 모두 카메라를 이동시켜 사용자가 조작 중일 때 점프가 발생할 수 있습니다. 한쪽만 트리거하거나, 최초 로드/사용자 액션 시로 한정하는 방식으로 정리해주세요.


103-104: 지도 패딩(320.dp)과 바텀시트 높이(340.dp) 불일치

NaverMap 하단 패딩(320.dp)과 하단 패널 높이(340.dp)가 달라, 리스트가 지도 위 요소를 가릴 수 있습니다. 단일 상수로 관리해 일치시키는 것을 권장합니다.
예:

  • val bottomSheetHeight = 340.dp를 정의 후 NaverMap에 .padding(bottom = bottomSheetHeight) 적용.

Also applies to: 202-206


111-121: Marker 상태 안정성 향상 제안

forEach 내에서 rememberMarkerState(position = …)를 직접 호출하면 항목 순서 변화 시 상태 재생성으로 깜빡임이 생길 수 있습니다. key(center.id)(또는 lat/long 조합)로 키를 부여하거나 리스트 기반 API로 키 안정성을 확보해주세요.


164-182: 접근성: 아이콘에 contentDescription 누락

현재 위치 아이콘에 적절한 contentDescription을 추가해주세요. 스크린 리더 사용성을 높입니다.
예: contentDescription = stringResource(R.string.home_extra_move_to_my_location)


185-198: i18n: 하드코딩 문자열 제거

"현 지도에서 검색" 문자열은 string 리소스로 이동해주세요. 테스트/현지화에 유리합니다.

app/src/main/java/com/example/findu/presentation/ui/extra/viewmodel/HomeExtraViewModel.kt (1)

95-101: 위치 업데이트 시 데이터 갱신 여부 확인

UpdateLocation은 좌표만 갱신하고 조회는 트리거하지 않습니다. 의도라면 OK이고, 아니라면 센터/부서 재조회(또는 사용자의 “현 지도에서 검색” 액션만 허용) 정책을 명확히 해주세요.

app/src/main/java/com/example/findu/data/dataremote/service/InformationService.kt (1)

22-29: 파라미터 이름 sidodistrict로 정정 권장

애노테이션은 @Query("district")인데 파라미터 이름이 sido라 혼동 소지가 큽니다(특히 named argument 사용 시). district로 일치시켜 주세요. 동시에 longlng(또는 lon)로 통일하면 가독성이 좋아집니다.

app/src/main/java/com/example/findu/domain/repository/InformationRepository.kt (2)

18-19: 파라미터 네이밍 개선 제안.

long 파라미터명이 명확하긴 하지만, 더 명시적인 이름을 사용하면 가독성이 올라갈 것 같아요. lon 또는 longitude를 고려해보세요.

     suspend fun getCenters(
         lastId: Long? = null,
         district: String? = null,
         lat: Double? = null,
-        long: Double? = null,
+        lon: Double? = null,
         size: Int? = null
     ): Result<PagedResult<Center>>

9-32: KDoc 문서화를 추가하면 좋을 것 같아요.

인터페이스에 각 메서드의 목적, 파라미터 설명, 반환값, 가능한 에러 케이스 등을 설명하는 KDoc을 추가하면 사용성이 크게 향상될 거예요. 특히 lastId, district, lat/long 같은 파라미터들의 의미와 PagedResult의 페이지네이션 동작 방식을 명확히 해주면 좋겠어요.

예시:

/**
 * 정보 조회를 위한 리포지토리 인터페이스
 */
interface InformationRepository {

    /**
     * 자원봉사 목록을 페이지네이션으로 조회합니다.
     *
     * @param lastId 마지막으로 조회한 항목의 ID (다음 페이지 조회 시 사용)
     * @return 자원봉사 목록을 담은 PagedResult
     */
    suspend fun getVolunteers(
        lastId: Long? = Long.MAX_VALUE
    ): Result<PagedResult<VolunteerWork>>

    // ... 나머지 메서드들도 동일하게 문서화
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e96b457 and 54f2617.

📒 Files selected for processing (54)
  • app/src/main/java/com/example/findu/data/dataremote/datasource/HomeRemoteDataSource.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/datasource/InformationRemoteDataSource.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/HomeRemoteDataSourceImpl.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/InformationRemoteDataSourceImpl.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/model/response/home/HomeResponseDto.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/model/response/information/CentersResponseDto.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/model/response/information/DepartmentsResponseDto.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/model/response/information/SidoListDto.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/model/response/information/SigunguListDto.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/model/response/information/VolunteersResponseDto.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/service/HomeService.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/service/InformationService.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/util/Constraints.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/mapper/toDomain/extra/SidoListDtoMapper.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/mapper/todomain/LoginResponseDtoMapper.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/mapper/todomain/extra/InformationResponseDtoMapper.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/mapper/todomain/home/HomeResponseDtoMapper.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/repositoryimpl/HomeRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/repositoryimpl/InformationRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/example/findu/di/DataSourceModule.kt (3 hunks)
  • app/src/main/java/com/example/findu/di/RepositoryModule.kt (3 hunks)
  • app/src/main/java/com/example/findu/di/ServiceModule.kt (2 hunks)
  • app/src/main/java/com/example/findu/di/UseCaseModule.kt (4 hunks)
  • app/src/main/java/com/example/findu/domain/model/extra/HomeExtraDataType.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/repository/InformationRepository.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/usecase/SetNicknameUseCase.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/usecase/auth/PostCheckNicknameUseCase.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/usecase/auth/PostGuestLoginUseCase.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/usecase/auth/PostLoginUseCase.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/usecase/auth/PostSignupUseCase.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/usecase/extra/GetCentersUseCase.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/usecase/extra/GetDepartmentsUseCase.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/usecase/extra/GetSidoUseCase.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/usecase/extra/GetSigunguUseCase.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/usecase/extra/GetVolunteersUseCase.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/usecase/home/GetHomeUseCase.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/base/TopAppBar.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraCenterItem.kt (2 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDepartmentItem.kt (3 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDistrictItem.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraVolunteerItem.kt (2 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (2 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraDepartmentScreen.kt (2 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraVolunteerScreen.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/extra/viewmodel/HomeExtraViewModel.kt (6 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/home/viewmodel/HomeViewModel.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/login/viewmodel/LoginViewModel.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/main/MainActivity.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/onboarding/viewmodel/OnboardingViewModel.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/splash/SplashActivity.kt (2 hunks)
  • app/src/main/res/drawable/ic_extra_district_dropdown_24.xml (1 hunks)
  • app/src/main/res/drawable/ic_place_now_24.xml (1 hunks)
  • app/src/main/res/values/strings.xml (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-16T15:20:00.175Z
Learnt from: t1nm1ksun
PR: FindYou-Kuit/FindYou-Android#77
File: app/src/main/java/com/example/findu/data/mapper/todomain/LoginResponseDtoMapper.kt:1-1
Timestamp: 2025-08-16T15:20:00.175Z
Learning: LoginResponseDtoMapper.kt와 관련된 패키지/디렉터리 구조는 이미 올바르게 com.example.findu.data.mapper.todomain으로 통일되어 있다. 초기 분석 시 더 신중하게 확인해야 한다.

Applied to files:

  • app/src/main/java/com/example/findu/data/mapper/todomain/LoginResponseDtoMapper.kt
  • app/src/main/java/com/example/findu/data/mapper/todomain/home/HomeResponseDtoMapper.kt
🧬 Code graph analysis (7)
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraVolunteerScreen.kt (1)
app/src/main/java/com/example/findu/presentation/ui/base/TopAppBar.kt (1)
  • FindUTopAppBar (19-66)
app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDistrictItem.kt (1)
app/src/main/java/com/example/findu/ui/theme/Theme.kt (1)
  • FindUTheme (39-59)
app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt (3)
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraVolunteerScreen.kt (1)
  • ExtraHomeVolunteerScreen (14-32)
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraDepartmentScreen.kt (1)
  • ExtraHomeDepartmentScreen (22-66)
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (1)
  • ExtraHomeCenterScreen (54-229)
app/src/main/java/com/example/findu/presentation/ui/extra/viewmodel/HomeExtraViewModel.kt (5)
app/src/main/java/com/example/findu/data/dataremote/datasource/InformationRemoteDataSource.kt (6)
  • getSigungu (30-30)
  • getSido (28-28)
  • getCenters (15-21)
  • getDepartments (23-26)
  • getVolunteers (10-31)
  • getVolunteers (11-13)
app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/InformationRemoteDataSourceImpl.kt (5)
  • getSigungu (37-38)
  • getSido (35-35)
  • getCenters (21-27)
  • getDepartments (30-33)
  • getVolunteers (17-19)
app/src/main/java/com/example/findu/data/dataremote/service/InformationService.kt (6)
  • getSigungu (40-42)
  • getSido (37-38)
  • getCenters (22-29)
  • getDepartments (31-35)
  • getVolunteers (16-43)
  • getVolunteers (17-20)
app/src/main/java/com/example/findu/data/repositoryimpl/InformationRepositoryImpl.kt (5)
  • getSigungu (57-62)
  • getSido (50-55)
  • getCenters (27-38)
  • getDepartments (40-48)
  • getVolunteers (18-25)
app/src/main/java/com/example/findu/domain/repository/InformationRepository.kt (6)
  • getSigungu (30-30)
  • getSido (28-28)
  • getCenters (15-21)
  • getDepartments (23-26)
  • getVolunteers (9-32)
  • getVolunteers (11-13)
app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraVolunteerItem.kt (1)
app/src/main/java/com/example/findu/presentation/ui/base/BaseVectorIcon.kt (1)
  • BaseVectorIcon (11-24)
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraDepartmentScreen.kt (2)
app/src/main/java/com/example/findu/presentation/ui/base/TopAppBar.kt (1)
  • FindUTopAppBar (19-66)
app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDistrictItem.kt (1)
  • ExtraDistrictItem (32-86)
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (4)
app/src/main/java/com/example/findu/presentation/ui/base/TopAppBar.kt (1)
  • FindUTopAppBar (19-66)
app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDistrictItem.kt (1)
  • ExtraDistrictItem (32-86)
app/src/main/java/com/example/findu/presentation/ui/base/BaseVectorIcon.kt (1)
  • BaseVectorIcon (11-24)
app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraCenterItem.kt (1)
  • ExtraCenterItem (30-92)
🔇 Additional comments (46)
app/src/main/res/drawable/ic_place_now_24.xml (1)

1-38: 좋네요! 위치 마커 아이콘이 깔끔하게 구현됐습니다.

네이버 지도 통합과 어울리는 24dp 위치 아이콘으로, 외부 원형 테두리, 십자 선, 중앙 채워진 원이 일관성 있게 디자인되었어요. 모든 요소에서 일관된 스트로크 색상과 너비를 사용하고 있고, SVG 경로도 올바르게 형성되어 있습니다.

app/src/main/java/com/example/findu/domain/usecase/auth/PostCheckNicknameUseCase.kt (1)

1-10: 패키지 구조 개선 잘 하셨습니다! 👍

인증 관련 유스케이스를 auth 서브패키지로 분리한 것은 코드 구조 개선에 좋은 선택입니다. 로직 변경 없이 깔끔하게 리팩토링되었네요.

app/src/main/java/com/example/findu/presentation/ui/onboarding/viewmodel/OnboardingViewModel.kt (1)

8-9: 임포트 경로 업데이트 완료! ✅

패키지 이동에 따른 임포트 경로 업데이트가 정확하게 되었습니다. 깔끔한 리팩토링이네요!

app/src/main/java/com/example/findu/domain/usecase/auth/PostSignupUseCase.kt (1)

1-1: 패키지 재정렬이 잘 정리됐네요.

auth 서브패키지로의 이동이 전체 PR의 구조 정리와 일관성 있게 진행되고 있습니다. 기능적 변화가 없어서 마이그레이션도 깔끔합니다.

app/src/main/java/com/example/findu/domain/usecase/auth/PostLoginUseCase.kt (2)

1-1: 패키지 구조 개선 👍

인증 관련 use case들을 auth 서브패키지로 분리한 것은 좋은 구조 개선입니다.


12-19: deviceId 초기화 검증 완료 - 문제 없음

검증 결과, deviceId 초기화는 안전하게 진행되고 있습니다:

  1. 동기식 초기화: setDeviceIdUseCase()는 suspend 함수가 아니며 synchronous하게 작동하므로, SplashActivity의 onCreate() 단계에서 즉시 완료됩니다 (라인 41).

  2. 영구 저장: DeviceLocalDataSource에 직접 저장되므로, PostLoginUseCase가 나중에 호출될 때 getDeviceId()를 통해 이미 설정된 값을 안정적으로 조회할 수 있습니다.

  3. Fallback 보장: Android ID 조회 실패 시 "unknown_device_id"로 폴백되므로 null이 될 수 없습니다.

코드는 현재 상태로 안전하며, 설정 및 조회 사이의 타이밍 문제는 없습니다.

app/src/main/java/com/example/findu/domain/usecase/auth/PostGuestLoginUseCase.kt (2)

1-1: 패키지 구조 개선 👍

다른 인증 use case들과 일관되게 auth 서브패키지로 이동되었습니다.


11-13: deviceId 초기화 검증 필요 (PostLoginUseCase와 동일)

게스트 로그인에서도 동일하게 deviceId를 사용하므로, PostLoginUseCase에서 언급한 deviceId 초기화 검증이 여기에도 적용됩니다.

app/src/main/java/com/example/findu/presentation/ui/login/viewmodel/LoginViewModel.kt (1)

7-8: import 경로 업데이트 확인 완료

Use case 패키지 구조 변경에 맞춰 import 경로가 올바르게 업데이트되었습니다.

app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraVolunteerItem.kt (2)

42-48: 기관명 텍스트 오버플로우 처리 잘 되었습니다.

긴 기관명이 UI를 깨뜨리지 않도록 maxLines=1, overflow=Ellipsis, weight=1f를 적절히 사용했네요. "상세보기" 버튼이 항상 보이도록 처리한 것도 좋습니다.


11-12: Material3 마이그레이션은 잘됐는데, 프로젝트 전체 상황을 확인해보니.. 🔍

현재 파일(ExtraVolunteerItem.kt)은 Material3로 잘 마이그레이션됐습니다. 👍

다만 전체 코드베이스를 봤을 때:

  • Material (구버전) 사용: ~40개 파일 (로그인, 리포트, 홈, 온보딩 등 대부분)
  • Material3 마이그레이션: ~9개 파일만 (extra 패키지 중심)

80% 이상이 아직 Material을 사용하고 있어서 상당히 불일치한 상태입니다.

이번 PR이 Material3 마이그레이션 시작이라면, 프로젝트 전체에서 일관된 마이그레이션 계획이 필요합니다. 혼용하면 나중에 유지보수 복잡도가 올라갈 수 있으니까요.

혹시 이번이 부분 마이그레이션인지, 아니면 전체 마이그레이션 계획의 일부인지 확인해주세요!

app/src/main/java/com/example/findu/domain/usecase/home/GetHomeUseCase.kt (1)

1-1: 패키지 이동 완벽하게 마무리됨 ✓

검증 결과 패키지 이동이 완전히 이루어졌습니다:

  • 구 패키지 경로(com.example.findu.domain.usecase)로부터의 임포트 없음
  • HomeViewModel.ktUseCaseModule.kt에서 새 경로(com.example.findu.domain.usecase.home) 임포트 확인
  • DI 모듈에서 provideGetHomeUseCase() 정상 작동
  • 모든 참조가 일관되게 업데이트됨

추가 조치 불필요합니다.

app/src/main/java/com/example/findu/data/dataremote/util/Constraints.kt (1)

24-26: <!-- [scratchpad_start]
[task review_file_2/5 verification_1/2]

  • INFORMATION constant usage verification: COMPLETE
  • Backend endpoint specification verification: INCOMPLETE (no docs in repo)
    [observations]
  • INFORMATION = "informations" defined at Constraints.kt:25
  • InformationService.kt uses $INFORMATION constant in 3 @get paths (lines 17, 22, 31):
    • /$API/$VERSION/$INFORMATION/volunteer-works
    • /$API/$VERSION/$INFORMATION/protection-centers
    • /$API/$VERSION/$INFORMATION/departments
  • Expands to: /api/v2/informations/volunteer-works|centers|departments
  • Constant imported and used correctly in InformationRemoteDataSourceImpl
  • No backend documentation found in repository
    [analysis]
  • Client code is internally consistent (plural form used everywhere)
  • All three endpoints consistently use the INFORMATION constant
  • Cannot verify if backend actually supports /informations endpoint
  • Risk: path mismatch would cause 404 errors, but this cannot be confirmed from codebase
    [pending]
  • Manual backend specification verification from developer
    [actions]
  • Flag for manual verification; note that client implementation is consistent
    [done]
  • Confirmed constant usage is consistent across all related endpoints
    [scratchpad_start] -->

서버 스펙 검증 필요

코드상 일관성은 확인됐어요. INFORMATION = "informations"이 InformationService의 3개 엔드포인트(/volunteer-works, /protection-centers, /departments)에서 모두 동일하게 사용 중입니다. 다만 저장소에 백엔드 API 문서가 없어서 서버가 실제로 /api/v2/informations/를 제공하는지 확인이 불가능하네요.

배포 전에 서버 스펙과 맞는지 한 번 더 확인하고, 만약 엔드포인트가 /information(단수)이라면 위험할 수 있으니 체크 부탁합니다.

app/src/main/res/values/strings.xml (1)

515-516: 새 문자열 리소스 추가 확인했습니다!

시/도와 시/군/구 선택을 위한 문자열이 잘 추가되었네요. 네이밍 규칙도 일관성 있게 유지되고 있습니다.

app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDepartmentItem.kt (2)

11-12: Material3 마이그레이션 좋습니다!

androidx.compose.material에서 androidx.compose.material3로 깔끔하게 전환하셨네요.


46-49: 긴 부서명 처리 잘하셨어요!

maxLines = 1TextOverflow.Ellipsis를 추가해서 긴 부서명이 UI를 깨지 않도록 잘 처리하셨습니다. weight(1f, fill = false) 조합도 적절하네요.

app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraVolunteerScreen.kt (1)

17-24: 뒷걸음질 기능 추가 좋습니다!

popBackStack 콜백을 추가해서 네비게이션 아이콘 클릭 시 뒤로가기가 동작하도록 하셨네요. 기본값도 안전하게 빈 람다로 설정되어 있어요.

app/src/main/java/com/example/findu/presentation/ui/splash/SplashActivity.kt (1)

31-41: 스플래시에서 디바이스 아이디 설정하도록 변경하셨네요.

PR 노트에서 언급하신 대로 디바이스 아이디를 스플래시 시점에 설정하도록 옮기셨습니다. Settings.Secure.ANDROID_ID가 null일 경우 "unknown_device_id"로 폴백하는 처리도 되어있어요.

정상 디바이스에서는 ANDROID_ID가 null이 되는 경우가 거의 없지만, 작성자님 환경에서 정상 동작 확인하셨다고 하시니 괜찮을 것 같습니다. 혹시 다른 분들 테스트에서 빈값이 들어가는지 확인 부탁드려요!

app/src/main/java/com/example/findu/data/dataremote/model/response/information/VolunteersResponseDto.kt (2)

6-14: 페이지네이션 구조가 잘 갖춰져 있어요.

lastIdisLast를 이용한 페이지네이션 패턴이 잘 적용되어 있습니다. 다만 lastId가 non-nullable Long인데, 마지막 페이지에서도 항상 값이 있는지 백엔드 API 스펙을 확인해보세요. 경우에 따라 Long?이 더 안전할 수 있어요.


16-30: 백엔드 API 명세 확인 필요

리뷰 코멘트의 제안은 타당합니다. 코드베이스 검증 결과:

  • VolunteerWorkDto의 모든 필드가 현재 non-nullable String으로 선언됨
  • 코드베이스 내 다른 DTO들(SearchAnimalCard, CentersResponseDto 등)에서는 실제로 nullable 필드(String?, Long?)를 사용하여 null 반환 가능성을 처리 중
  • 백엔드 API 명세를 확인해서 volunteerWorks 배열 내 각 필드가 null을 반환할 수 있다면 해당 필드를 nullable로 변경 권장
app/src/main/java/com/example/findu/data/dataremote/model/response/information/SigunguListDto.kt (1)

7-10: 코드 안정성을 위해 sigunguList를 nullable로 변경하거나 기본값 제공 권장

현재 코드는 List<String>으로 선언되어 있는데, InformationRepositoryImpl.kt의 61번 줄에서 직접 접근하고 있습니다. runCatching 래퍼가 있어서 역직렬화 실패 시 예외는 잡히지만, 더욱 견고하게 하려면:

  1. SigunguListDto에서 val sigunguList: List<String>? 또는 val sigunguList: List<String> = emptyList()로 변경
  2. 백엔드 API 스펙 확인해서 실제로 null을 반환할 가능성이 있는지 검증

현재는 에러 처리가 있어서 앱이 깨지지는 않지만, 명시적 방어가 더 좋습니다.

app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDistrictItem.kt (2)

32-86: 드롭다운 구현이 깔끔하네요!

제네릭을 사용한 설계가 좋습니다. 한 가지 확인할 점이 있는데, Line 72에서 ExposedDropdownMenuColor.Transparent 배경을 설정했는데, 의도한 디자인이 맞나요? 배경이 복잡한 화면에서 메뉴 가독성이 떨어질 수 있습니다.


89-108: 프리뷰 구현 잘 되어 있습니다!

로컬 상태 관리와 샘플 데이터를 통해 컴포넌트 사용법을 명확하게 보여주고 있습니다.

app/src/main/java/com/example/findu/data/dataremote/model/response/information/DepartmentsResponseDto.kt (1)

1-29: 부서 목록 DTO 구조가 잘 정의되어 있습니다!

페이지네이션을 위한 lastIdisLast 필드가 적절하게 포함되어 있고, 직렬화 어노테이션도 올바르게 적용되어 있습니다.

app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraCenterItem.kt (2)

11-12: Material3로 마이그레이션 굿!

DividerText를 Material3로 변경한 것이 다른 파일들과 일관성 있게 잘 적용되었습니다.


98-105: 프리뷰 데이터에 좌표 추가 확인!

latitudelongitude 필드 추가가 DTO 변경사항과 잘 맞아떨어집니다. 네이버 지도 연동을 위한 준비가 되어 있네요.

app/src/main/java/com/example/findu/data/mapper/todomain/home/HomeResponseDtoMapper.kt (1)

1-7: 패키지 정리 잘 하셨습니다!

HomeResponseDtoresponse.home 서브패키지로 이동한 것이 구조적으로 더 깔끔해 보입니다. 매퍼 로직은 그대로 유지되어 안전한 리팩토링입니다.

app/src/main/java/com/example/findu/presentation/ui/home/viewmodel/HomeViewModel.kt (1)

9-9: UseCase 패키지 이동 확인!

GetHomeUseCasedomain.usecase.home 서브패키지로 정리한 것이 일관성 있게 적용되었습니다.

app/src/main/java/com/example/findu/data/mapper/toDomain/extra/SidoListDtoMapper.kt (1)

1-12: 시도 목록 매퍼 깔끔합니다!

간단하고 명확한 DTO to Domain 매핑입니다. 확장 함수 패턴을 잘 활용했습니다.

app/src/main/java/com/example/findu/data/repositoryimpl/HomeRepositoryImpl.kt (1)

5-5: 매퍼 import 경로 업데이트 확인!

toDomain 확장 함수를 todomain.home 패키지로 정리한 것이 잘 반영되었습니다.

app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/HomeRemoteDataSourceImpl.kt (1)

5-5: DTO import 경로 정리 완료!

HomeResponseDtoresponse.home 패키지로 이동한 변경사항이 일관성 있게 적용되었습니다. 전체적인 패키지 정리가 깔끔하게 마무리되었네요.

app/src/main/java/com/example/findu/data/dataremote/datasource/HomeRemoteDataSource.kt (1)

4-4: 패키지 리팩토링 확인 완료

HomeResponseDto의 패키지 이동에 맞춰 import 경로가 잘 업데이트되었습니다.

app/src/main/java/com/example/findu/di/RepositoryModule.kt (1)

80-82: InformationRepository DI 바인딩 추가 확인

InformationRepository의 DI 바인딩이 기존 패턴대로 잘 추가되었습니다.

app/src/main/java/com/example/findu/data/dataremote/model/response/home/HomeResponseDto.kt (1)

1-1: 패키지 구조 개선 확인

Home 관련 DTO를 별도 서브패키지로 분리하여 구조가 더 명확해졌습니다.

app/src/main/java/com/example/findu/data/dataremote/service/HomeService.kt (1)

4-4: Import 경로 업데이트 확인

HomeResponseDto의 새 패키지 경로로 import가 정상 업데이트되었습니다.

app/src/main/java/com/example/findu/di/ServiceModule.kt (1)

74-77: InformationService 프로바이더 추가 확인

InformationService의 Retrofit 서비스 프로바이더가 기존 패턴대로 잘 추가되었습니다.

app/src/main/java/com/example/findu/di/DataSourceModule.kt (1)

100-102: InformationRemoteDataSource 바인딩 추가 확인

데이터 소스 바인딩이 기존 패턴대로 잘 추가되었습니다.

app/src/main/java/com/example/findu/domain/usecase/extra/GetSidoUseCase.kt (1)

8-12: GetSidoUseCase 구현 확인

@Singleton@Inject 어노테이션이 잘 적용되어 있고, 표준 UseCase 패턴을 따르고 있습니다.

app/src/main/java/com/example/findu/data/dataremote/model/response/information/SidoListDto.kt (1)

1-18: 불필요한 검증 요청입니다. 수정 불필요

kotlinx.serialization은 기본적으로 라이브러리와 함께 ProGuard 규칙을 제공하며, 이 규칙들이 축소 후 유지되는 모든 직렬화 가능한 클래스의 직렬화기를 보존하므로 추가 설정이 필요하지 않습니다. 명명된 companion 객체가 있는 클래스를 직렬화하려는 경우에만 추가 규칙이 필요합니다—이는 매우 드문 경우입니다.

현재 코드베이스에서 SidoListDto와 SidoItemDto는 표준 @serializable 데이터 클래스이므로 명시적인 ProGuard 규칙 파일이 필요하지 않습니다. 라이브러리가 모든 필요한 보호를 처리합니다.

app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraDepartmentScreen.kt (1)

20-21: ExposedDropdownMenu API는 현재 Material3 1.3.2에서 여전히 Experimental 상태입니다

프로젝트에서 사용 중인 Material3 버전은 1.3.2이며, ExposedDropdownMenuBox와 관련 API들이 현재 @ExperimentalMaterial3Api로 표기되어 있습니다. ExtraDistrictItem.kt의 @OptIn(ExperimentalMaterial3Api::class) 주석이 적절하게 적용되어 있고, 호출 측(ExtraDepartmentScreen, ExtraCenterScreen)에서는 이미 stabilized된 컴포넌트를 사용하므로 추가 opt-in이 필요하지 않습니다.

현재 구현이 올바르므로 별도 조치는 불필요합니다.

app/src/main/java/com/example/findu/data/mapper/todomain/extra/InformationResponseDtoMapper.kt (1)

12-26: 매핑 로직 전반 LGTM

필드 매핑 정확하고, 페이징 메타(lastId/isLast) 전달도 적절합니다. 추가 이슈 없습니다.

도메인 모델 nullable 제약과 DTO가 일치하는지만 한 번만 점검해주세요(예: phoneNumber 공백/누락 케이스).

Also applies to: 28-42, 44-55

app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt (2)

76-89: <!-- [scratchpad_start]
[task review_comment verification 1/1]

[observations]

  • File: HomeExtraViewModel.kt lines 85-88 shows SidoSelected event handler
  • Line 86: _uiState.update { it.copy(selectedSido = event.selectedSido, selectedSigungu = EMPTY_STRING) } - selectedSigungu reset confirmed
  • Line 87: getSigungu(event.selectedSido.id) - calls sigunguList reload
  • File: HomeExtraViewModel.kt lines 230-236 shows getSigungu() implementation
  • Line 233: _uiState.update { it.copy(sigunguList = result) } - sigunguList updated with new result
  • File: HomeExtraFragment.kt lines 79-81 passes uiState values to ExtraHomeDepartmentScreen
  • File: ExtraDepartmentScreen.kt lines 52-53 receives and displays selectedSigungu and sigunguList in UI
  • popBackStack callback at line 88 of HomeExtraFragment.kt: { findNavController().popBackStack() } - created inline each render

[analysis]

  • Synchronization concern: When Sido selected, selectedSigungu is reset to "" AND getSigungu() is called immediately in same event handler
  • Flow is correct: user selects Sido → SidoSelected event → selectedSigungu set to empty + getSigungu() fetches new list → UI receives both updated values
  • Dropdown mismatch risk: MITIGATED - empty selectedSigungu prevents stale selection, new sigunguList arrives before next user interaction
  • popBackStack optimization: Creating lambda each time is minor inefficiency; remember would prevent recreation but not critical for functionality

[analysis_complete]
The reviewer's concern about synchronization is already properly handled in the current implementation. The optional refactor suggestion is valid but not essential.
[scratchpad_end]
-->

시/군구 동기화는 이미 올바르게 구현되어 있습니다.

시도 선택 시 동기화 흐름을 확인했습니다:

  • SidoSelected 이벤트에서 selectedSigungu를 즉시 빈 문자열로 초기화하고 getSigungu()를 호출 (HomeExtraViewModel.kt:86-87)
  • getSigungu()는 비동기로 새 시군구 목록을 받아서 sigunguList 업데이트 (HomeExtraViewModel.kt:233)
  • UI는 두 상태 변화를 모두 감지하므로 드롭다운 불일치 없음

popBackStack 콜백의 remember 최적화는 선택사항입니다. 현재 구현도 함수적으로 문제없지만, 불필요한 람다 재생성을 피하고 싶으면 고려할 수 있습니다.


93-111: SearchCenterFocusedLatLng 연속 호출 방어 추가 필요 + 디바이스 ID 초기화 타이밍 확인 권장

  • 클릭 난사 시 SearchCenterFocusedLatLng 연속 호출 방지: 현재 VM 레벨에서 throttle/debounce가 없어 API 폭주 위험. 500ms 정도의 짧은 debounce 추가 권장.
  • 위치 기본값: 이미 37.56, 127.00 (서울 중심) 기본값이 설정되어 있으므로 0.0 우려는 낮음. 다만 GPS 권한 미획득 시 명시적 폴백 로직 추가 검토.
  • 디바이스 ID 초기화 타이밍: SplashActivity.onCreate()에서 설정되는데, HomeExtraFragment 진입 시점에 아직 설정되지 않았다면 빈 문자열 상태로 남을 수 있음. 특히 auth 요청 헤더/파라미터 사용 시 주의 필요.
app/src/main/java/com/example/findu/di/UseCaseModule.kt (2)

241-258: 정보 UseCase 바인딩 LGTM

DI 스코프/의존성 모두 정상입니다. 별도 이슈 없어 보입니다.


277-287: 시·군·구 조회 UseCase 추가 LGTM

초기 화면 진입 시 Sido 프리로드 시나리오에 유용합니다. 별도 문제 없습니다.

app/src/main/java/com/example/findu/domain/repository/InformationRepository.kt (1)

11-13: getVolunteers의 lastId 기본값이 다른 메서드들과 실제로 일관성이 없습니다.

코드 검증 결과, getVolunteers는 전체 계층(도메인, 원격 데이터소스, 서비스, UseCase)에서 Long.MAX_VALUE를 기본값으로 사용하고 있는 반면, getCentersgetDepartmentsnull을 사용하고 있습니다. 이 차이는 일관성 있는 설계가 아닙니다.

다만 변경하기 전에 확인이 필요합니다:

  • 백엔드 API가 volunteer-works 엔드포인트에서 Long.MAX_VALUE를 "첫 페이지"로 특별 처리하는지 확인하세요
  • 만약 백엔드가 Long.MAX_VALUEnull을 동일하게 처리한다면, 일관성을 위해 null로 변경하는 것이 좋습니다
  • 만약 백엔드가 Long.MAX_VALUE를 특정 로직으로 처리하고 있다면, 현재 설계는 의도적일 수 있습니다

Comment on lines +15 to +21
suspend fun getCenters(
lastId: Long? = null,
district: String? = null,
lat: Double? = null,
long: Double? = null,
size: Int? = null
): BaseResponse<CentersResponseDto>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

경도 파라미터 명확화 + 페이지 크기 기본값

  • longlng로 리네임을 권장합니다(타입 Long과의 혼동 방지, 지도 도메인 컨벤션 일치).
  • size는 서버 기본 페이지 크기 상수(예: Constraints.DEFAULT_PAGE_SIZE)가 있다면 기본값을 지정해 주는 것이 좋습니다.
     suspend fun getCenters(
         lastId: Long? = null,
         district: String? = null,
         lat: Double? = null,
-        long: Double? = null,
+        lng: Double? = null,
         size: Int? = null
     ): BaseResponse<CentersResponseDto>

호출부/서비스 인터페이스까지 일괄 리네임 필요 여부를 점검해 주세요.
검사용 스크립트:


🏁 Script executed:

#!/bin/bash
rg -nP -C2 '\bgetCenters\s*\(' --type=kotlin
rg -nP -C2 '\blong\s*:\s*Double\?' --type=kotlin

Length of output: 10106


파라미터 이름 표준화 필요 — 현재 lon / long 혼용 상태

스크립트 결과에서 파라미터 이름이 계층별로 불일치함을 확인했습니다:

  • GetCentersUseCase.kt: lon 사용
  • InformationRepository / RepositoryImpl / Service / RemoteDataSourceImpl / RemoteDataSource: long 사용
  • HomeExtraViewModel 호출부: lon 사용

이름 변경 시 다음 파일들을 모두 함께 수정해야 합니다:

  • InformationRemoteDataSource.kt (line 19)
  • InformationRemoteDataSourceImpl.kt (line 25)
  • InformationService.kt (line 27)
  • InformationRepository.kt (line 19)
  • InformationRepositoryImpl.kt (line 31)

추가로 InformationService.kt@Query("long")는 백엔드 API 요구사항인지 확인 후, 필요하면 @Query("lng")로도 함께 변경해야 합니다.

🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/data/dataremote/datasource/InformationRemoteDataSource.kt
around lines 15–21, the longitude parameter name is inconsistent with other
layers (uses "long" here but other files use "lon"); rename the parameter from
long to lon in this file's function signature and update all call sites and
overrides accordingly (also update InformationRemoteDataSourceImpl.kt line 25,
InformationService.kt line 27, InformationRepository.kt line 19, and
InformationRepositoryImpl.kt line 31 to use the same name), and when updating
InformationService.kt confirm the backend query parameter name—if backend
expects "lng" change the @Query value to "lng" while keeping the parameter name
consistent as lon in Kotlin signatures.

Comment on lines +10 to +15
suspend operator fun invoke(
lastId: Long? = null,
district: String? = null,
lat: Double? = null,
lon: Double? = null,
size: Int? = null
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

좌표는 둘 다 null이거나 둘 다 값이 있어야 합니다 (부분 입력 방지)

lat만(or lon만) 전달되는 케이스를 선제 차단하지 않으면 요청이 깨질 수 있어요. 간단한 입력 검증을 추가해 주세요.

     suspend operator fun invoke(
         lastId: Long? = null,
         district: String? = null,
         lat: Double? = null,
         lon: Double? = null,
         size: Int? = null
     ): Result<PagedResult<Center>> = repository.getCenters(
+        // 입력 검증 예시: 좌표는 둘 다 null 또는 둘 다 non-null
+        // require((lat == null) == (lon == null)) { "lat/lon은 함께 설정되거나 함께 비워져야 합니다." }

필요하다면 size 범위 검증(예: 1..100)도 함께 고려해 주세요.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/src/main/java/com/example/findu/domain/usecase/extra/GetCentersUseCase.kt
around lines 10 to 15, add input validation so latitude and longitude are either
both null or both non-null (reject cases where only one is provided) and
return/throw a clear validation error; implement this by checking (lat == null)
xor (lon == null) and handling it appropriately before proceeding. Also
optionally validate size bounds (e.g., require size in 1..100) and normalize or
reject out-of-range values with a clear error message.

Comment on lines +5 to +9
class GetSigunguUseCase (
private val repository: InformationRepository
) {
suspend operator fun invoke(sidoId: Long): Result<List<String>> = repository.getSigungu(sidoId = sidoId)
} No newline at end of file
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

의존성 주입 어노테이션 누락

GetSigunguUseCase@Inject@Singleton 어노테이션이 누락되어 Hilt가 이 클래스를 주입할 수 없습니다. 같은 패키지의 GetSidoUseCase와 달리 DI 어노테이션이 빠져있어 런타임 시 주입 실패가 발생합니다.

다음 diff를 적용하여 DI 어노테이션을 추가하세요:

+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
-class GetSigunguUseCase (
+class GetSigunguUseCase @Inject constructor(
     private val repository: InformationRepository
 ) {
     suspend operator fun invoke(sidoId: Long): Result<List<String>> = repository.getSigungu(sidoId = sidoId)
 }
🤖 Prompt for AI Agents
In app/src/main/java/com/example/findu/domain/usecase/extra/GetSigunguUseCase.kt
around lines 5 to 9, the class is missing Hilt DI annotations so it cannot be
injected; add the @Singleton annotation to the class and annotate its
constructor with @Inject (i.e., make it an @Singleton class with an @Inject
constructor that takes the InformationRepository) and add the necessary imports
for javax.inject.Singleton and javax.inject.Inject (or dagger.hilt annotations
if your project uses those) so Hilt can provide this use case.

Comment on lines +138 to +149
private fun getCenters(
sido: String = uiState.value.selectedSido.name,
sigungu : String = uiState.value.selectedSigungu,
lat : Double = uiState.value.latitude,
lon:Double = uiState.value.longitude
) {
viewModelScope.launch {
getCentersUseCase(
district = sido + sigungu,
lat = lat,
lon = lon
).fold(
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

버그: 행정구역(district) 문자열 공백 없이 결합됨

district = sido + sigungu는 "서울특별시강남구"처럼 공백이 사라져 서버 필터가 실패할 수 있습니다. 공백 포함 결합(필요 시 null 처리)을 적용하세요.

제안 패치:

-            getCentersUseCase(
-                district = sido + sigungu,
-                lat = lat,
-                lon = lon
-            ).fold(
+            getCentersUseCase(
+                district = listOf(sido, sigungu).filter { it.isNotBlank() }.joinToString(" ").ifEmpty { null },
+                lat = lat,
+                lon = lon
+            ).fold(

보조 함수로 재사용도 가능합니다:

private fun buildDistrict(sido: String, sigungu: String): String? =
    listOf(sido, sigungu).filter { it.isNotBlank() }.joinToString(" ").ifBlank { null }
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/extra/viewmodel/HomeExtraViewModel.kt
around lines 138 to 149, the district is built by concatenating sido + sigungu
which removes the separating space (e.g., "서울특별시강남구") and can break server
filtering; change to build a district string that joins non-blank sido and
sigungu with a single space and returns null if the result is blank (or handle
null where used), ideally by creating a small helper like buildDistrict(sido,
sigungu) that filters blank parts, joins with " ", and maps blank to null, then
use that helper when calling getCentersUseCase.

Comment on lines +171 to +176
private fun getDepartments() {
viewModelScope.launch {
getDepartmentsUseCase(district = uiState.value.selectedSido.name + uiState.value.selectedSigungu).fold(
onSuccess = { list ->
_uiState.update {
it.copy(
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

동일 이슈: 부서 조회 district 결합 로직

여기도 공백 없이 결합됩니다. 위와 동일한 방식으로 수정해주세요.

제안 패치:

-            getDepartmentsUseCase(district = uiState.value.selectedSido.name + uiState.value.selectedSigungu).fold(
+            getDepartmentsUseCase(
+                district = listOf(uiState.value.selectedSido.name, uiState.value.selectedSigungu)
+                    .filter { it.isNotBlank() }
+                    .joinToString(" ")
+                    .ifEmpty { null }
+            ).fold(
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/extra/viewmodel/HomeExtraViewModel.kt
around lines 171-176, the district string is currently formed by concatenating
selectedSido.name and selectedSigungu without a separator; change the
concatenation to include a space (or appropriate separator) between the two
values (e.g., selectedSido.name + " " + selectedSigungu or use string
interpolation), ensuring the same null-safety/format used elsewhere in the
codebase.

Copy link

@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 (7)
app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt (3)

40-43: fusedLocationProvider 초기화 컨텍스트 일관화 제안

requireActivity() 대신 requireContext()를 쓰면 액티비티 참조를 불필요하게 붙잡지 않고도 동일 동작을 얻습니다. lazy 자체는 OK.

다음처럼 변경을 고려해 주세요:

- LocationServices.getFusedLocationProviderClient(requireActivity())
+ LocationServices.getFusedLocationProviderClient(requireContext())

156-188: lastLocation 단일 경로는 NULL/STALE 가능 — 현재 위치 Fallback 권장

lastLocation이 null이거나 오래된 값일 수 있어 신뢰도가 떨어집니다. 실패/NULL 시 getCurrentLocation(Priority…)로 한 번 더 시도하고, 그래도 실패하면 LoadData로 폴백하는 흐름을 권장합니다. 토스트 문자열은 string 리소스로 이동해 주세요.

예시(핵심 로직만):

fusedLocationClient.lastLocation
  .addOnSuccessListener { location ->
    if (location != null) {
      homeExtraViewModel.handleEvent(HomeExtraUiEvent.UpdateLocation(location.latitude, location.longitude))
      homeExtraViewModel.handleEvent(HomeExtraUiEvent.LoadData)
    } else {
      val cts = CancellationTokenSource()
      fusedLocationClient.getCurrentLocation(Priority.PRIORITY_BALANCED_POWER_ACCURACY, cts.token)
        .addOnSuccessListener { current ->
          current?.let {
            homeExtraViewModel.handleEvent(HomeExtraUiEvent.UpdateLocation(it.latitude, it.longitude))
          }
          homeExtraViewModel.handleEvent(HomeExtraUiEvent.LoadData)
        }
        .addOnFailureListener { homeExtraViewModel.handleEvent(HomeExtraUiEvent.LoadData) }
    }
  }
  .addOnFailureListener { homeExtraViewModel.handleEvent(HomeExtraUiEvent.LoadData) }

추가 필요 import:
import com.google.android.gms.location.Priority
import com.google.android.gms.tasks.CancellationTokenSource


84-90: StateFlow에 distinctUntilChanged 적용 권장 - 중복 이벤트 트리거 방지

검증 결과, _uiState = MutableStateFlow(HomeExtraUiState())distinctUntilChanged가 미적용되어 있습니다. 같은 homeExtraButtonType 값이 반복 업데이트되어도 LaunchedEffect(uiState.homeExtraButtonType) (84-90줄)가 매번 재실행되어 requestLocationAndLoadData() 중복 호출 가능성이 실제로 존재합니다.

HomeExtraViewModel의 uiState 노출 시점(109-114줄)에 .distinctUntilChanged()를 추가하면 불필요한 중복 트리거를 방지할 수 있습니다.

app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (4)

99-106: 센터 도착 시 자동 카메라 점프 조건부 처리 권장

센터 목록이 갱신될 때마다 첫 센터로 카메라가 이동하면 사용자 제스처를 방해할 수 있습니다. 최초 1회만 이동하거나, 사용자가 직접 이동했는지 플래그로 구분하세요.

예:

var autoCentered by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(centers, autoCentered) {
  if (!autoCentered && centers.isNotEmpty()) {
    cameraPositionState.animate(CameraUpdate.scrollTo(...))
    autoCentered = true
  }
}

127-138: Marker에 안정적 key 부여로 상태 보존

루프 안 rememberMarkerState는 키 없이 재구성 시 상태 매칭이 흔들릴 수 있습니다. 각 마커에 안정적인 key를 부여하세요.

- visibleCenters.forEach { center ->
-     Marker(
-         state = rememberMarkerState(position = LatLng(center.latitude, center.longitude)),
-         icon = OverlayImage.fromResource(R.drawable.ic_home_extra_volunteer_gps_20)
-     )
- }
+ visibleCenters.forEach { center ->
+     key("${center.latitude},${center.longitude},${center.centerName}") {
+         Marker(
+             state = rememberMarkerState(position = LatLng(center.latitude, center.longitude)),
+             icon = OverlayImage.fromResource(R.drawable.ic_home_extra_volunteer_gps_20)
+         )
+     }
+ }

(필요 시 import androidx.compose.runtime.key)


215-228: 하드코딩 텍스트/접근성

  • "현 지도에서 검색 " 문자열과 토스트 문구는 string 리소스로 이동 권장.
  • 위치 아이콘에 contentDescription 추가해 접근성 보완.
- BaseVectorIcon(
+ BaseVectorIcon(
    vectorResource = R.drawable.ic_place_now_24,
    modifier = ...,
+   contentDescription = stringResource(R.string.cd_move_to_my_location)
 )
- Text(text = "현 지도에서 검색 ", ...)
+ Text(text = stringResource(R.string.home_extra_search_here), ...)

(주의: R.string.cd_move_to_my_location, R.string.home_extra_search_here 추가 필요)


118-121: 320dp/340dp 매직 넘버로 레이아웃 간섭 가능

맵 패딩(320dp)과 바텀 시트 높이(340dp)가 고정/상이하여 기기별 레이아웃 겹침 위험이 있습니다. 시트 실제 높이를 onGloballyPositioned로 측정해 맵 패딩에 반영하거나, ModalBottomSheet 등 컴포저블을 사용해 자동 레이아웃 조정을 맡기는 방식을 권장합니다.

예(개념):

var sheetHeightPx by remember { mutableIntStateOf(0) }
val sheetHeightDp = with(LocalDensity.current) { sheetHeightPx.toDp() }

NaverMap(modifier = Modifier.padding(bottom = sheetHeightDp).fillMaxSize(), ...)

Column(modifier = Modifier.onGloballyPositioned { sheetHeightPx = it.size.height } /* ... */) { ... }

Also applies to: 144-146, 232-256

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 54f2617 and 653c8e5.

📒 Files selected for processing (2)
  • app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt (3)
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraVolunteerScreen.kt (1)
  • ExtraHomeVolunteerScreen (14-32)
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraDepartmentScreen.kt (1)
  • ExtraHomeDepartmentScreen (22-66)
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (1)
  • ExtraHomeCenterScreen (61-259)
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (4)
app/src/main/java/com/example/findu/presentation/ui/base/TopAppBar.kt (1)
  • FindUTopAppBar (19-66)
app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDistrictItem.kt (1)
  • ExtraDistrictItem (32-86)
app/src/main/java/com/example/findu/presentation/ui/base/BaseVectorIcon.kt (1)
  • BaseVectorIcon (11-24)
app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraCenterItem.kt (1)
  • ExtraCenterItem (30-92)
🔇 Additional comments (2)
app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt (1)

121-143: 스크린 파라미터 전달 구조는 명확합니다

선택 상태와 핸들러, 좌표 전달 방식 모두 일관적입니다. 👍

app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (1)

261-302: 프리뷰 구성 좋습니다

API 변경 사항을 반영한 파라미터/더미 데이터가 일관적입니다. 👍

Comment on lines +80 to +87
val permissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
onResult = { isGranted ->
if (!isGranted) {
Toast.makeText(context, "위치 권한이 거부되었습니다.", Toast.LENGTH_SHORT).show()
}
}
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

권한 런처를 복수 권한으로 통일 + 문자열 리소스화

Fragment는 FINE/COARSE 동시 요청인데, 여기서는 단일(FINE)만 요청합니다. COARSE만 허용된 기기에서 반복 프롬프트가 발생할 수 있어 UX 저하입니다. 복수 권한으로 통일하고 토스트 문자열은 리소스로 이동해 주세요.

- val permissionLauncher = rememberLauncherForActivityResult(
-     contract = ActivityResultContracts.RequestPermission(),
-     onResult = { isGranted ->
-         if (!isGranted) {
-             Toast.makeText(context, "위치 권한이 거부되었습니다.", Toast.LENGTH_SHORT).show()
-         }
-     }
- )
+ val permissionLauncher = rememberLauncherForActivityResult(
+     contract = ActivityResultContracts.RequestMultiplePermissions(),
+     onResult = { perms ->
+         val granted = perms[Manifest.permission.ACCESS_FINE_LOCATION] == true ||
+                       perms[Manifest.permission.ACCESS_COARSE_LOCATION] == true
+         if (!granted) {
+             Toast.makeText(context, context.getString(R.string.permission_location_denied), Toast.LENGTH_SHORT).show()
+         }
+     }
+ )

(주의: R.string.permission_location_denied 추가 필요)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt
around lines 80 to 87, the permission launcher currently requests only a single
fine location permission causing inconsistent behavior versus the Fragment
(which requests both FINE and COARSE) and using a hardcoded toast string; change
the launcher to rememberLauncherForActivityResult with
ActivityResultContracts.RequestMultiplePermissions(), handle the Map<String,
Boolean> result to treat either ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION
being true as granted (and show the denied toast only if both are false), and
replace the hardcoded message with
context.getString(R.string.permission_location_denied) (add
R.string.permission_location_denied to resources if not present).

Comment on lines +180 to +212
BaseVectorIcon(
vectorResource = R.drawable.ic_place_now_24,
modifier = Modifier
.roundedBackgroundWithPadding(
backgroundColor = FindUTheme.colors.white,
cornerRadius = 12.dp,
padding = PaddingValues(12.dp)
)
.noRippleClickable {
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) -> {
scope.launch {
cameraPositionState.animate(
CameraUpdate.scrollAndZoomTo(
LatLng(latitude, longitude), INITIAL_ZOOM_LEVEL
)
)
}
searchCurrentLocation(LatLng(latitude, longitude))
}

else -> {
Toast
.makeText(context, "현재 위치로 이동하려면 위치 권한이 필요합니다.", Toast.LENGTH_SHORT)
.show()
permissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}
}
}
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

현재 위치 버튼: 권한 판별/요청 로직 정합성

여기서는 FINE만 체크합니다. COARSE만 허용된 경우에도 계속 FINE을 재요청하게 됩니다. Fragment와 동일 기준(둘 중 하나 허용 시 진행)으로 맞춰 주세요. 또한 카메라 이동 자체는 권한 없이도 가능한 UI 동작이어서 “권한 없으면 이동 불가” UX는 굳이 필요 없습니다.

- when (PackageManager.PERMISSION_GRANTED) {
-   ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) -> {
-     // animate & search
-   }
-   else -> {
-     Toast.makeText(context, "현재 위치로 이동하려면 위치 권한이 필요합니다.", Toast.LENGTH_SHORT).show()
-     permissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
-   }
- }
+ val hasLocation = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
+                   ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
+ if (hasLocation) {
+   scope.launch {
+     cameraPositionState.animate(CameraUpdate.scrollAndZoomTo(LatLng(latitude, longitude), INITIAL_ZOOM_LEVEL))
+   }
+   searchCurrentLocation(LatLng(latitude, longitude))
+ } else {
+   Toast.makeText(context, context.getString(R.string.permission_location_required_for_current_place), Toast.LENGTH_SHORT).show()
+   permissionLauncher.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION))
+ }

(주의: R.string.permission_location_required_for_current_place 추가 필요)

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Collaborator

@ikseong00 ikseong00 left a comment

Choose a reason for hiding this comment

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

좋습니다! 특별히 리뷰할 부분 없이 깔끔하게 잘 짜신 것 같아요!

@ikseong00 ikseong00 merged commit dda6820 into develop Oct 29, 2025
1 check passed
@coderabbitai coderabbitai bot mentioned this pull request Nov 5, 2025
1 task
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.

[feat] 홈 추가화면 API 연동

2 participants