Skip to content

Comments

[Feat] search api#96

Merged
nasohee merged 22 commits intodevelopfrom
feat-search-api
Oct 13, 2025
Merged

[Feat] search api#96
nasohee merged 22 commits intodevelopfrom
feat-search-api

Conversation

@nasohee
Copy link
Collaborator

@nasohee nasohee commented Oct 6, 2025

Related issue 🛠

Work Description 📝

  • 기존 작성되어있는 api를 수정하여 새로운 api를 적용하였습니다.
  • 빠뜨린 기능들 몇 개 있어서 함께 구현해주었습니다.
  • 필터 fragment 파일이 너무 큰 거 같아 분리 했습니다.

Screenshot 📸

2025-10-08.19.11.22.mov

Uncompleted Tasks 😅

  • 필터 적용

To Reviewers 📢

스크린샷 2025-10-08 19 09 45

네이버맵 인증 실패가 떠서 화면에 뜨질 않습니다. 이 문제만 해결하면 될 거 같습니다!

글고 디바이스 id땜에 앱 실행을 못했어서.. 임시로 파일 수정했는데 머지할때 다시 복구해놓겠습니다..!

Summary by CodeRabbit

  • 신기능

    • 실종/목격/보호 상세 화면 분리: 좌표 기반 지도 표시, 이미지 슬라이더, 즐겨찾기 토글, 주소 복사 지원
    • 통합 검색/필터 화면 추가: 기간·종/품종·지역 선택 및 결과 반영
  • 개선

    • 검색 목록 카드 및 페이지네이션 정리: reportId 기반, 썸네일 null 대응, lastId 단일화
    • 검색 목록 UI/인터랙션 개선: 그리드/가로 전환, 배너 이동, 새로운 북마크 아이콘, 둥근 모서리 이미지
    • 내 정보 화면 버전 비교 로직 고도화
  • 제거

    • 기존 더미 데이터 및 이전 필터 UI 제거

@coderabbitai
Copy link

coderabbitai bot commented Oct 6, 2025

Walkthrough

검색/상세 조회 전반을 v2 API로 재구성. 목록 검색은 단일 getReports(type, …, lastId)로 통합, 상세는 보호/실종/목격 3종으로 분리. DTO·도메인·매퍼·레포지토리·유스케이스·뷰모델·UI 전 계층을 이에 맞춰 개편하고, 필터 화면을 신규 구조로 재구성. 빌드에 Firebase 추가.

Changes

Cohort / File(s) Summary
Remote APIs (Interfaces/Services)
.../datasource/SearchRemoteDataSource.kt, .../datasource/DetailSearchRemoteDataSource.kt, .../service/SearchService.kt, .../service/DetailSearchService.kt
검색 API 3종 → 단일 getReports(type, lastId)로 통합. 상세 API는 reportId 경로로 보호/실종/목격 3종 추가 및 경로 v2로 변경.
Remote Implementations
.../datasourceimpl/SearchRemoteDataSourceImpl.kt, .../datasourceimpl/DetailSearchRemoteDataSourceImpl.kt
구현을 단일 getReports 호출로 정리, 상세는 missing/witness/protect별 메서드로 분리. 요청 파라미터/매핑 갱신.
DTO Models (search/)
.../model/response/search/SearchResponseDto.kt, .../search/DetailProtectResponseDto.kt, .../search/DetailMissingResponseDto.kt, .../search/DetailWitnessResponseDto.kt
패키지 이동 및 스키마 변경: lastId 단일화, cardId→reportId, 썸네일 nullable. 보호/실종/목격 상세 DTO 신규/개편.
Removed DTO/Domain
.../model/response/DetailReportResponseDto.kt, .../domain/model/search/DetailReportData.kt
기존 Report 상세 모델 제거.
Domain Models
.../domain/model/search/DetailProtectData.kt, .../domain/model/search/DetailMissingData.kt, .../domain/model/search/DetailWitnessData.kt, .../domain/model/search/SearchData.kt
보호 데이터 개편(필드/타입 변경), 실종/목격 데이터 신설, SearchData의 lastId 단일화 및 item 필드(reportId, thumbnail nullable 등) 반영.
Mappers
.../data/mapper/todomain/SearchResponseDtoMapper.kt, .../data/mapper/todomain/DetailSearchResponseDtoMapper.kt, .../presentation/mapper/todomain/MyInterestDataMapper.kt, .../presentation/ui/search/model/SearchFilterUiModel.kt
DTO→도메인 매핑을 새 스키마에 맞게 수정/추가. 문자열→상태 변환 추가. 패키지/함수명 정리(toDomain).
Repositories
.../data/repositoryimpl/SearchRepositoryImpl.kt, .../data/repositoryimpl/DetailSearchRepositoryImpl.kt, .../domain/repository/SearchRepository.kt, .../domain/repository/DetailSearchRepository.kt
검색: 단일 getReports로 통합. 상세: report 제거, missing/witness 추가, protect 파라미터명 정리.
UseCases
.../domain/usecase/GetSearchUseCase.kt, .../domain/usecase/GetDetailSearchUseCase.kt
검색 단일화(getReports), 상세는 missing/witness 전용 유스케이스 메서드 추가.
ViewModels
.../presentation/ui/search/viewmodel/SearchViewModel.kt, .../viewmodel/DetailSearchViewModel.kt, .../viewmodel/SearchFilterViewModel.kt, .../viewmodel/DetailReportViewModel.kt (removed)
검색 단일 진입점 및 필터 상태 정리/초기화 추가. 상세는 3종 데이터 스트림과 관심 토글 추가. 필터 전용 VM 신설. 기존 Report VM 제거.
Search UI: Adapter/Listener/Models
.../presentation/ui/search/adapter/SearchListAdapter.kt, .../presentation/ui/search/adapter/SearchListListener.kt, .../presentation/ui/search/model/SearchRv.kt, .../presentation/ui/search/model/SearchType.kt, .../presentation/ui/search/adapter/SearchFilterLocationRVAdapter.kt
어댑터 리스너 기반으로 재구성, 동일성 키 reportId로 변경, 위치 필드명 address→location. SearchType 도입. 불필요 업데이트 메서드 제거.
Search UI: Fragments (tabs/detail)
.../tablayout/SearchAllFragment.kt, .../tablayout/SearchReportFragment.kt, .../tablayout/SearchRescueFragment.kt, .../detail/SearchDisappearDetailFragment.kt, .../detail/SearchProtectingDetailFragment.kt, .../detail/SearchWitnessDetailFragment.kt
탭: 통합 getSearchData(type, lastId)와 필터 연동. 상세: 실종/목격/보호 전용 플로우, 좌표 기반 지도, 이미지 ViewPager, 클립보드 복사, 관심 토글 경로 갱신.
Filter UI (new)
.../presentation/ui/search/filter/SearchFilterFragment.kt, .../filter/SearchSpeciesSelector.kt, .../filter/SearchBreedSelector.kt, .../filter/SearchLocationSelector.kt, .../filter/SearchCalendarSelector.kt
필터 화면과 선택기 유틸 신설(종/품종/지역/기간). 종 선택시 품종 로드, 적용/초기화/저장 흐름 구성.
Filter UI (removed/renamed)
.../presentation/ui/search/SearchFilterFragment.kt (removed), .../presentation/ui/search/SearchFilterBottomSheet.kt (removed), .../presentation/ui/search/model/DummySearchData.kt (removed), .../presentation/ui/search/model/SearchDummyProvider.kt (removed), .../presentation/ui/search/BundleTag.kt
구 필터/바텀시트/더미 데이터 제거. 번들 키 FILTER_RESULTS 제거.
Layouts/Drawables/Nav
.../res/layout/fragment_search_detail_disappear.xml, .../res/layout/fragment_search_detail_protecting.xml, .../res/layout/item_search_detail_img.xml, .../res/layout/search_horizontal_content_item.xml, .../res/layout/fragment_search_filter.xml, .../res/drawable/bg_radius_20.xml, .../res/drawable/ic_search_blank_bookmark_horizontal.xml, .../res/navigation/nav_graph.xml
상세 이미지 ViewPager 도입/스타일 추가, 텍스트 ID 정리, 수평 아이템 북마크 아이콘 교체, 필터 프래그먼트 경로/인자(SearchType) 갱신, 새 배경/아이콘 리소스 추가.
Build/Deps
build.gradle.kts, app/build.gradle.kts, gradle/libs.versions.toml
Google Services 플러그인 및 Firebase BOM/ktx 의존성 추가.
Misc
.../presentation/ui/my/MyFragment.kt, .../presentation/ui/my/MyViewModel.kt, .../data/mapper/todomain/LoginResponseDtoMapper.kt
버전 비교 유틸 추가 및 정리, 임포트/패키지 경로 정리.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor UI as SearchFragment/Tab
  participant VM as SearchViewModel
  participant UC as GetSearchUseCase
  participant Repo as SearchRepository
  participant DS as SearchRemoteDataSource
  participant Svc as SearchService (/api/v2/reports)

  UI->>VM: getSearchData(type, lastId, filter)
  VM->>UC: getReports(type, filter, lastId)
  UC->>Repo: getReports(type, filter, lastId)
  Repo->>DS: getReports(type, filter, lastId)
  DS->>Svc: GET /api/v2/reports?type=&lastId=...
  Svc-->>DS: BaseResponse<SearchResponseDto>
  DS-->>Repo: SearchResponseDto
  Repo-->>UC: List<SearchData>
  UC-->>VM: List<SearchData>
  VM-->>UI: 상태 업데이트(성공/에러)
  note over UI,VM: lastId 기반 페이지네이션
Loading
sequenceDiagram
  autonumber
  actor UI as DetailFragment (Missing/Witness/Protect)
  participant VM as DetailSearchViewModel
  participant UC as GetDetailSearchUseCase
  participant Repo as DetailSearchRepository
  participant DS as DetailSearchRemoteDataSource
  participant Svc as DetailSearchService (v2)

  UI->>VM: getDetailSearch{Type}(reportId)
  VM->>UC: get{Type}Data(reportId)
  UC->>Repo: getDetailSearch{Type}(reportId)
  Repo->>DS: getDetailSearch{Type}(reportId)
  DS->>Svc: GET /api/v2/reports/{type}-reports/{report_id}
  Svc-->>DS: BaseResponse<Detail{Type}ResponseDto>
  DS-->>Repo: DTO
  Repo-->>UC: Detail{Type}Data
  UC-->>VM: Detail{Type}Data
  VM-->>UI: UI 바인딩 및 isInterested 반영

  rect rgba(230,255,230,0.3)
  note right of VM: 관심 토글<br/>toggleInterest{Type}(id)
  VM->>Svc: POST/DELETE 관심 API
  Svc-->>VM: 결과
  VM-->>UI: isInterested 갱신
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • FindYou-Android#80: 동일한 검색 UI/필터/상세 흐름과 어댑터·레이아웃을 대폭 수정하는 PR로, 이번 통합 검색/상세 개편과 코드 레벨에서 직접 맞물립니다.
  • FindYou-Android#75: MyFragment의 버전/프로필 관련 로직을 수정하는 변경이 포함되어 본 PR의 MyFragment 정리와 충돌 가능성이 높습니다.

Suggested reviewers

  • ikseong00

Poem

새 길로 묶인 검색선, 한 줄로 흘러가고
셋 갈래 상세 바람, 좌표 따라 닿는다
보고·실종·목격, 카드엔 보고서 하나
lastId는 등불, 페이지 끝 비춘다
북마크 톡— 별 하나 더 달고 ⭐
우리 앱은 오늘도, 차분히 업! 🚀

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning PR에는 검색 API 연동 및 필터 Fragment 분리와 직접 관련 없는 Firebase 플러그인 및 의존성 추가, MyFragment 버전 체크 로직 도입, LoginResponseDtoMapper의 불필요한 import 수정 등의 변경이 포함되어 있어 범위를 벗어난 작업이 일부 확인됩니다. 검색 API 연동과 필터 분리 작업에 집중할 수 있도록 Firebase 설정 및 MyFragment 관련 변경 사항을 별도 PR로 분리하거나 해당 부분을 제거하는 것을 권장합니다.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed 제목 “[Feat] search api”는 이번 PR의 핵심 변경 사항인 검색 API 연동을 간결하게 표현하고 있어 PR 내용을 빠르게 파악하기에 적절합니다.
Linked Issues Check ✅ Passed PR에서는 이슈 #91의 목표인 조회 API 연동을 위해 SearchService, RemoteDataSource, Repository, UseCase, ViewModel, UI 전반에 걸쳐 getReports 메서드를 도입하고 관련 로직을 연결하여 이슈의 주요 요구사항을 충족하고 있습니다.
Description Check ✅ Passed PR 설명은 템플릿에 정의된 Related issue, Work Description, Screenshot, Uncompleted Tasks, To Reviewers 섹션을 모두 포함하고 있어 요구되는 구조와 내용을 잘 준수하고 있습니다.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-search-api

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d473f0 and 233c2aa.

📒 Files selected for processing (1)
  • app/src/main/res/navigation/nav_graph.xml (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/res/navigation/nav_graph.xml

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.

@nasohee nasohee changed the title Feat search api [Feat] search api Oct 6, 2025
@nasohee nasohee self-assigned this Oct 6, 2025
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: 4

Caution

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

⚠️ Outside diff range comments (1)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (1)

83-143: 지도 위치 반영 타이밍을 보완해주세요

setupMap()이 지도 준비보다 먼저 호출되면 naverMap이 항상 null이라 카메라 이동/마커 표시가 전혀 되지 않습니다. 사용자는 위치 정보를 볼 수 없으니 즉시 수정이 필요합니다.

지도 콜백에서 최신 좌표를 다시 적용할 수 있도록 보관해 두고, naverMap이 초기화된 뒤에 재호출하는 방식으로 보완해 주세요.

@@
-private var naverMap: NaverMap? = null
+private var naverMap: NaverMap? = null
+private var pendingLocation: LatLng? = null
@@
-        binding.mapView.getMapAsync { nMap ->
-            naverMap = nMap
-        }
+        binding.mapView.getMapAsync { nMap ->
+            naverMap = nMap
+            pendingLocation?.let { location ->
+                setupMap(location.latitude, location.longitude)
+            }
+        }
@@
-    private fun setupMap(lat: Double, lon: Double) {
-        val location = LatLng(lat, lon)
-        naverMap?.moveCamera(CameraUpdate.scrollTo(location))
-        Marker().apply {
-            position = location
-            map = naverMap
-            icon = OverlayImage.fromResource(R.drawable.ic_search_map_marker)
-            height = 23
-        }
+    private fun setupMap(lat: Double, lon: Double) {
+        val location = LatLng(lat, lon)
+        val map = naverMap
+        if (map == null) {
+            pendingLocation = location
+            return
+        }
+        pendingLocation = null
+        map.moveCamera(CameraUpdate.scrollTo(location))
+        Marker().apply {
+            position = location
+            this.map = map
+            icon = OverlayImage.fromResource(R.drawable.ic_search_map_marker)
+            height = 23
+        }
     }
🧹 Nitpick comments (6)
app/src/main/res/layout/fragment_search_detail_protecting.xml (1)

51-58: ViewPager2 패딩이 페이지 폭을 깎고 있습니다

ViewPager2paddingStart/End를 주면 내부 리사이클러뷰 폭이 그만큼 줄어들어 카드가 원래 디자인보다 40dp 좁아집니다. 바깥 여백이 목적이라면 패딩 대신 layout_marginStart/End(또는 감싸는 레이아웃에 margin/padding)을 사용하는 편이 안전합니다.

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

13-14: AuthRepositoryImpl에서와 동일한 매퍼 import 이슈입니다.

toDomaintodomain 패키지에서 같은 이름의 함수를 import하는 패턴이 여러 파일에서 반복되고 있네요. 이는 코드베이스 전체적으로 개선이 필요한 부분입니다.

매퍼 패키지 구조를 정리해서 하나의 일관된 패키지로 통합하는 것을 추천드립니다.

app/src/main/java/com/example/findu/domain/model/search/DetailWitnessData.kt (1)

5-18: 목격 정보 도메인 모델이 잘 구성되었습니다.

모든 필드가 불변(val)으로 선언되어 있고, 타입도 적절합니다. 다만 witnessLocationwitnessAddress 두 필드가 모두 있는데, API 스펙상 둘 다 필요한 것이 맞는지 확인해보시면 좋을 것 같아요. (만약 하나는 간단한 지역명, 하나는 상세 주소라면 괜찮습니다!)

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

10-19: type 파라미터에 대한 검증을 고려해보세요.

현재 typeString으로 받고 있는데, SearchType 상수가 정의되어 있으니 잘못된 값이 전달될 가능성이 있습니다. 타입 안전성을 위해 enum이나 sealed class를 사용하거나, 최소한 유효성 검증 로직을 추가하는 것을 권장합니다.

예시:

suspend fun getReports(
    type: String,
    searchFilterData: SearchFilterData?,
    lastId: Long = Long.MAX_VALUE
): Result<List<SearchData>> {
    require(type in setOf(SearchType.ALL, SearchType.REPORTING, SearchType.PROTECTING)) {
        "Invalid search type: $type"
    }
    return searchRepository.getReports(
        type = type,
        searchFilterData = searchFilterData,
        lastId = lastId
    )
}
app/src/main/java/com/example/findu/presentation/ui/search/model/SearchType.kt (1)

3-7: 타입 안전성을 위해 enum 사용을 고려해보세요.

현재 objectconst val String으로 정의되어 있는데, enum class를 사용하면 컴파일 타임에 타입 체크가 가능하고 when 표현식에서 exhaustive check도 받을 수 있습니다.

예시:

enum class SearchType(val value: String) {
    ALL("ALL"),
    REPORTING("REPORTING"),
    PROTECTING("PROTECTING")
}

이렇게 하면 GetSearchUseCase에서도 String 대신 SearchType enum을 받을 수 있습니다.

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

12-66: 매퍼 함수들이 중복된 패턴을 따르고 있습니다.

세 개의 매퍼 함수(DetailProtectResponseDto.toDomain(), DetailMissingResponseDto.toDomain(), DetailWitnessResponseDto.toDomain())가 모두 단순 필드 복사 패턴을 따르고 있습니다. 이런 보일러플레이트 코드는 Kotlin의 data class copy나 리플렉션을 활용하거나, Mapper 라이브러리(MapStruct 등)를 고려해볼 수 있습니다. 다만 현재 규모에서는 명시적인 매핑이 가독성과 유지보수에 더 나을 수 있으니 선택사항입니다.

Based on learnings

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad7b5e8 and a378241.

📒 Files selected for processing (43)
  • app/src/main/java/com/example/findu/data/dataremote/datasource/DetailSearchRemoteDataSource.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/datasource/SearchRemoteDataSource.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/DetailSearchRemoteDataSourceImpl.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/SearchRemoteDataSourceImpl.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/model/response/DetailReportResponseDto.kt (0 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/model/response/search/DetailMissingResponseDto.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/model/response/search/DetailProtectResponseDto.kt (2 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/model/response/search/DetailWitnessResponseDto.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/model/response/search/SearchResponseDto.kt (2 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/service/DetailSearchService.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/service/SearchService.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/mapper/todomain/DetailSearchResponseDtoMapper.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/mapper/todomain/SearchResponseDtoMapper.kt (2 hunks)
  • app/src/main/java/com/example/findu/data/repositoryimpl/AuthRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/repositoryimpl/DetailSearchRepositoryImpl.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/ReportRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/repositoryimpl/SearchRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/model/search/DetailMissingData.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/model/search/DetailProtectData.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/model/search/DetailReportData.kt (0 hunks)
  • app/src/main/java/com/example/findu/domain/model/search/DetailWitnessData.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/model/search/SearchData.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/repository/DetailSearchRepository.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/repository/SearchRepository.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/usecase/GetDetailSearchUseCase.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/usecase/GetSearchUseCase.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/adapter/SearchListAdapter.kt (3 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (7 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/model/DummySearchData.kt (0 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/model/SearchDummyProvider.kt (0 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/model/SearchRv.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/model/SearchType.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (6 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/viewmodel/DetailReportViewModel.kt (0 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/viewmodel/DetailSearchViewModel.kt (2 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/viewmodel/SearchViewModel.kt (3 hunks)
  • app/src/main/res/layout/fragment_search_detail_disappear.xml (2 hunks)
  • app/src/main/res/layout/fragment_search_detail_protecting.xml (2 hunks)
💤 Files with no reviewable changes (5)
  • app/src/main/java/com/example/findu/presentation/ui/search/model/DummySearchData.kt
  • app/src/main/java/com/example/findu/presentation/ui/search/model/SearchDummyProvider.kt
  • app/src/main/java/com/example/findu/data/dataremote/model/response/DetailReportResponseDto.kt
  • app/src/main/java/com/example/findu/presentation/ui/search/viewmodel/DetailReportViewModel.kt
  • app/src/main/java/com/example/findu/domain/model/search/DetailReportData.kt
🧰 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/DetailSearchResponseDtoMapper.kt
  • app/src/main/java/com/example/findu/data/mapper/todomain/SearchResponseDtoMapper.kt
🧬 Code graph analysis (6)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (4)
  • initTagView (196-203)
  • initViewPager (143-160)
  • setupMap (85-94)
  • updateBookmarkUI (233-238)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (4)
  • initTagView (180-187)
  • initViewPager (132-149)
  • setupMap (77-86)
  • updateBookmarkUI (216-221)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (7)
  • observeViewModel (105-120)
  • fetchDetailData (95-103)
  • initListener (166-181)
  • initTagView (206-213)
  • initViewPager (147-164)
  • setupMap (83-92)
  • updateBookmarkUI (243-248)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (7)
  • observeViewModel (98-112)
  • fetchDetailData (88-96)
  • initListener (151-165)
  • initTagView (180-187)
  • initViewPager (132-149)
  • setupMap (77-86)
  • updateBookmarkUI (216-221)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (5)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (1)
  • observeViewModel (107-121)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (1)
  • observeViewModel (105-120)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (1)
  • observeViewModel (98-112)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (3)
  • observeViewModel (58-79)
  • setupRV (81-102)
  • navigateToDetail (131-158)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (3)
  • observeViewModel (61-82)
  • setupRV (84-105)
  • navigateToDetail (107-134)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (5)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (1)
  • observeViewModel (107-121)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (1)
  • observeViewModel (105-120)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (1)
  • observeViewModel (98-112)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (3)
  • observeViewModel (58-79)
  • setupRV (81-102)
  • navigateToDetail (131-158)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (3)
  • observeViewModel (63-84)
  • setupRV (113-134)
  • navigateToDetail (136-163)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (4)
  • initTagView (196-203)
  • initViewPager (143-160)
  • setupMap (85-94)
  • updateBookmarkUI (233-238)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (4)
  • initTagView (206-213)
  • initViewPager (147-164)
  • setupMap (83-92)
  • updateBookmarkUI (243-248)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (3)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (1)
  • observeViewModel (107-121)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (3)
  • observeViewModel (63-84)
  • setupRV (113-134)
  • navigateToDetail (136-163)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (3)
  • observeViewModel (61-82)
  • setupRV (84-105)
  • navigateToDetail (107-134)
🔇 Additional comments (24)
app/src/main/java/com/example/findu/data/repositoryimpl/HomeRepositoryImpl.kt (1)

5-6: toDomain import은 서로 다른 패키지의 확장 함수를 제공합니다.

현재 빌드 오류나 불필요한 import가 없으므로 제거가 필요하지 않습니다. 다만 CamelCase vs 소문자 패키지 네이밍 통일을 이후 리팩터링으로 검토해주세요.

Likely an incorrect or invalid review comment.

app/src/main/java/com/example/findu/domain/model/search/DetailProtectData.kt (1)

5-26: 도메인 모델 구조가 잘 정리되었습니다.

모든 필드가 val로 선언되어 불변성이 보장되고, imageUrlsList<String>으로 변경되어 여러 이미지를 지원할 수 있게 되었네요. 지리 좌표(latitude, longitude) 추가로 지도 기능 구현도 가능해졌습니다.

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

7-48: DTO 구조가 API 응답에 맞게 잘 구성되었습니다.

모든 필드에 @SerialName 어노테이션이 올바르게 적용되어 JSON 역직렬화가 안정적으로 처리될 것으로 보입니다. 도메인 모델과 타입이 일치하여 매핑도 원활할 것 같네요.

app/src/main/java/com/example/findu/presentation/ui/search/model/SearchRv.kt (1)

12-12: 필드명 변경이 API v2에 맞게 잘 적용되었습니다.

cardId에서 reportId로의 네이밍 변경이 도메인 의미를 더 명확하게 표현하네요.

app/src/main/java/com/example/findu/domain/model/search/SearchData.kt (2)

5-9: 페이징 필드가 깔끔하게 통합되었습니다.

lastProtectIdlastReportIdlastId 하나로 통합하여 구조가 단순해졌네요. API v2의 단일 엔드포인트 방식에 잘 맞는 변경입니다.


11-19: SearchAnimal 모델 개선이 좋습니다.

thumbnailImageUrl을 nullable로 변경한 것은 이미지가 없는 경우를 안전하게 처리할 수 있게 해주네요. location에서 address로의 네이밍 변경도 더 명확합니다.

app/src/main/res/layout/fragment_search_detail_disappear.xml (1)

306-306: 레이아웃 ID 변경이 일관성 있게 적용되었습니다.

tv_value_lost_location에서 tv_value_lost_address로 ID가 변경되고, 관련된 모든 제약 조건 참조도 정확하게 업데이트되었네요. 도메인 모델의 네이밍 변경과 잘 맞습니다.

Also applies to: 321-323

app/src/main/java/com/example/findu/presentation/ui/search/adapter/SearchListAdapter.kt (1)

37-37: 필드명 변경이 일관되게 적용되었습니다.

cardId에서 reportId로의 변경이 DiffUtil의 identity 비교와 북마크 콜백에 모두 정확히 반영되었습니다.

Also applies to: 115-115, 147-147

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

11-18: 상세 API가 명확하게 분리되었습니다.

getMissingDatagetWitnessData로의 분리가 깔끔하게 적용되었고, 기존 getProtectData와 일관된 패턴을 따르고 있습니다.

app/src/main/java/com/example/findu/domain/model/search/DetailMissingData.kt (1)

5-21: 도메인 모델이 깔끔하게 정의되었습니다.

Serializable을 구현하여 Intent 전달이 가능하고, DTO와 필드가 일대일로 매칭됩니다. DTO에서 imageUrls가 기본값을 가지므로 이 모델에서도 안전하게 non-nullable로 받을 수 있습니다.

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

8-24: null 처리 일관성 확인
imageUrls만 기본값(emptyList())을 설정하고 나머지 필드는 non-nullable로 정의되어 있어, API가 null 또는 해당 필드를 누락할 경우 역직렬라이즈 시 예외가 발생할 수 있습니다. API 스펙에 따라 나머지 필드에도 기본값 부여 또는 nullable 타입 적용을 검토해야 합니다.

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

20-26: listOf 래핑은 Result<List<SearchData>> 반환 타입을 맞추기 위함입니다.
toDomain()이 반환하는 SearchDataList<SearchData>로 감싸는 동작으로, 현재 의도된 구현이 맞다면 그대로 유지하세요.

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

9-11: 세분화된 상세 조회 엔드포인트 확인했습니다.

타입별 메소드 분리가 명확해져 이후 구현부 추적이 수월해졌네요.

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

13-26: 단일 lastId 및 필드 맵핑 교체 잘 반영됐습니다.

새 DTO 구조를 자연스럽게 도메인으로 옮겨와서 이후 뷰모델 연동에 문제 없어 보입니다.

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

6-20: 목격 상세 DTO 정의 깔끔합니다.

필드 명세가 API 스펙과 일치해 보이고 기본값 처리도 안정적입니다.

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

72-131: 검색 호출 단일화 흐름이 명확해졌습니다.

타입별 필터 매핑과 상태 갱신이 한곳에서 처리돼 유지보수가 한결 쉬워졌네요.

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

9-18: v2 단일 엔드포인트 선언 깔끔합니다.

쿼리 파라미터 기본값 정리가 잘 되어 초기 호출과 페이지네이션 모두 대응되겠네요.

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

8-12: 데이터소스 시그니처 통합 잘 적용됐습니다.

타입과 필터를 묶어 전달해 서비스 레이어와 일관성이 맞춰졌어요.

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

15-40: 상세 리포지토리 처리 흐름 안정적입니다.

공통 패턴으로 예외 처리와 매핑을 통일해 재사용성이 좋아졌습니다.

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

9-13: 도메인 계약 분리가 명쾌합니다.

타입별 메소드가 명시돼 호출부에서도 명확히 의도를 표현할 수 있겠네요.

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

11-24: 엔드포인트 전환 문제 없음

v2 경로와 DTO 매핑이 깔끔하게 정리됐고, Path 파라미터도 일관적으로 교체되었습니다. 그대로 진행해도 될 것 같아요.

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

7-11: 리포지토리 API 단일화 확인

검색 진입점이 하나로 합쳐져 도메인 인터페이스가 더 깔끔해졌네요. 문제 없이 보입니다.

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

13-26: 원격 호출 경로 정상

service.getReports 호출로 파라미터가 모두 자연스럽게 매핑되어 있어요. 별다른 이슈 보이지 않습니다.

app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (1)

17-186: 목격 상세 뷰 흐름 OK

새 도메인 모델과 뷰모델로 자연스럽게 전환되었고, 지도/태그/이미지 처리도 일관적입니다. 그대로 유지하셔도 될 듯해요.

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

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/presentation/ui/search/adapter/SearchListAdapter.kt (1)

112-118: 수평 이미지 로딩에서 HTTP 변환 누락

Line 112에서 여전히 item.image를 그대로 Glide에 전달하고 있어 HTTP URL이 남습니다. 앱이 cleartext 트래픽을 차단하면 이 경로의 이미지는 계속 로딩에 실패합니다. Grid 쪽처럼 HTTPS로 변환해 주세요.

-                Glide.with(root.context).load(item.image).into(ivSearchContent)
+                Glide.with(root.context)
+                    .load(item.image.replace("http://", "https://"))
+                    .into(ivSearchContent)
app/src/main/java/com/example/findu/data/mapper/todomain/SearchResponseDtoMapper.kt (1)

19-29: 매퍼에서 thumbnailImageUrl null을 빈 문자열로 대체하지 말고 null로 유지하고, UI에서 placeholder/error 핸들링 추가

  • 매퍼(SearchResponseDtoMapper.kt 등)에서 thumbnailImageUrl ?: "" 대신 String?으로 null 유지
  • Glide/Coil·AsyncImage 호출 시 item.thumbnailImageUrl.ifBlank { null }으로 null 전달하고, .placeholder(), .error() 또는 AsyncImage의 placeholder/error 속성 지정해 빈 화면 방지
♻️ Duplicate comments (1)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (1)

52-55: 🔴 Critical: 이전 리뷰에서 지적된 크래시 버그가 여전히 수정되지 않았습니다

onCreateView에서 observeViewModel()을 호출하면 내부에서 viewLifecycleOwner.lifecycleScope (lines 59, 72)에 접근하려고 시도하는데, 이 시점에는 viewLifecycleOwner가 아직 초기화되지 않아서 IllegalStateException이 발생합니다.

이전 리뷰에서도 동일한 이슈를 지적했었는데 수정이 누락된 것 같습니다. observeViewModel() 호출을 반드시 onViewCreated로 옮겨주세요.

다음 diff를 적용하여 수정해주세요:

 override fun onCreateView(
     inflater: LayoutInflater, container: ViewGroup?,
     savedInstanceState: Bundle?,
 ): View {
     _binding = FragmentSearchAllBinding.inflate(inflater, container, false)
     initRVAdapter()
-    observeViewModel()
-    viewModel.getSearchData(SearchType.ALL,lastId)
     return binding.root
 }

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     super.onViewCreated(view, savedInstanceState)
+    observeViewModel()
+    viewModel.getSearchData(SearchType.ALL, lastId)

     childFragmentManager.setFragmentResultListener(
🧹 Nitpick comments (10)
app/src/main/java/com/example/findu/domain/usecase/PostGuestLoginUseCase.kt (2)

8-8: 사용하지 않는 import를 제거해주세요.

DeviceRepository를 더 이상 사용하지 않으므로 import 구문을 삭제하면 좋겠습니다.

-import com.example.findu.domain.repository.DeviceRepository

14-22: 하드웨어 ID 사용과 테스트 가능성에 대해 고려해보세요.

Settings.Secure.ANDROID_ID를 직접 사용하는 방식으로 변경되었는데, 몇 가지 참고할 점이 있습니다:

  1. 프라이버시: 하드웨어 ID는 사용자 추적에 사용될 수 있어 Android에서 주의를 권장합니다. @SuppressLint로 경고를 억제하셨으니 의도하신 것 같긴 한데, 게스트 로그인에서 정말 디바이스 ID가 필요한지 확인해보시면 좋을 것 같아요.

  2. 테스트 가능성: ContextSettings를 직접 사용하면 unit test에서 모킹이 어려워집니다. 이전의 DeviceRepository 방식이 테스트하기에는 더 좋았어요.

  3. 빈 문자열 처리: ANDROID_ID가 null일 때 빈 문자열을 반환하는데, 백엔드에서 빈 deviceId를 어떻게 처리하는지 확인이 필요할 것 같습니다.

테스트 작성 시 Robolectric이나 instrumented test가 필요할 수 있습니다. DeviceRepository의 구현만 수정하는 방향도 대안이 될 수 있어요.

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

186-190: DI 제공 함수 패턴 일관성 검토
PostGuestLoginUseCase만 Context를 주입하도록 변경된 반면, PostLoginUseCase(182줄)와 PostSignupUseCase(203줄)는 여전히 DeviceRepository를 사용 중입니다. 의존성 주입 방식을 통일하거나 DeviceRepository 구현을 Context 기반으로 전환하는 방향을 고려해주세요.

app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (1)

45-45: 미사용 변수 제거 권장

items 변수가 선언되어 있지만 실제로는 사용되지 않습니다. 제거해도 될 것 같습니다.

-    private var items = ArrayList<SearchAnimal>()
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (3)

90-90: location null 처리 추가 권장

SearchAllFragment에서는 location = item.location ?: ""로 null-safe 처리를 했는데, 여기서는 누락되었습니다. 일관성을 위해 동일하게 처리하면 좋을 것 같습니다.

-                location = item.location,
+                location = item.location ?: "",

46-46: 미사용 변수 제거 권장

items 변수가 선언되어 있지만 실제로는 사용되지 않습니다.

-    private var items = ArrayList<SearchAnimal>()

61-105: 코드 중복 고려

observeViewModel()setupRV() 로직이 SearchAllFragment, SearchReportFragment와 거의 동일합니다. 추후 시간이 되면 Base Fragment를 만들거나 공통 로직을 추출하는 것도 고려해볼 만합니다.

app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (2)

119-119: location null 처리 추가 권장

SearchAllFragment에서는 ?: "" 처리가 되어 있는데 여기는 누락되었습니다. 일관성을 위해 추가하면 좋겠습니다.

-                location = item.location,
+                location = item.location ?: "",

47-47: 미사용 변수 제거 권장

items 변수가 사용되지 않습니다.

-    private var items = ArrayList<SearchAnimal>()
app/src/main/java/com/example/findu/data/mapper/todomain/SearchResponseDtoMapper.kt (1)

32-39: 예외 대신 안전한 폴백을 고려해보세요.

알 수 없는 태그값에 대해 IllegalArgumentException을 던지면 런타임에 앱이 크래시될 수 있습니다. SearchStatus.UNKNOWN이 이미 정의되어 있으니, 예외 대신 UNKNOWN으로 폴백하는 게 더 안전할 것 같아요. 서버에서 새로운 상태값이 추가되더라도 앱이 죽지 않겠죠.

 fun String.toSearchStatus(): SearchStatus {
     return when (this.trim()) {
         "보호중" -> SearchStatus.PROTECTING
         "목격신고" -> SearchStatus.WITNESS
         "실종신고" -> SearchStatus.MISSING
-        else -> throw IllegalArgumentException("Unknown tag value: $this")
+        else -> SearchStatus.UNKNOWN
     }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a378241 and 4ad1bdb.

📒 Files selected for processing (12)
  • app/src/main/AndroidManifest.xml (1 hunks)
  • app/src/main/java/com/example/findu/data/dataremote/model/response/search/SearchResponseDto.kt (2 hunks)
  • app/src/main/java/com/example/findu/data/mapper/todomain/SearchResponseDtoMapper.kt (2 hunks)
  • app/src/main/java/com/example/findu/di/UseCaseModule.kt (3 hunks)
  • app/src/main/java/com/example/findu/domain/model/search/SearchData.kt (1 hunks)
  • app/src/main/java/com/example/findu/domain/usecase/PostGuestLoginUseCase.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/my/MyFragment.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/adapter/SearchListAdapter.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/model/SearchRv.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (6 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (5 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/SearchResponseDtoMapper.kt
🧬 Code graph analysis (3)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (3)
  • observeViewModel (58-79)
  • setupRV (81-102)
  • navigateToDetail (131-158)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (3)
  • observeViewModel (63-84)
  • setupRV (113-134)
  • navigateToDetail (136-163)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (3)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (3)
  • observeViewModel (58-79)
  • setupRV (81-102)
  • navigateToDetail (131-158)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (3)
  • observeViewModel (61-82)
  • setupRV (84-105)
  • navigateToDetail (107-134)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (1)
  • observeViewModel (105-120)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (2)
  • observeViewModel (63-84)
  • setupRV (113-134)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (2)
  • observeViewModel (61-82)
  • setupRV (84-105)
🔇 Additional comments (5)
app/src/main/AndroidManifest.xml (1)

21-21: network_security_config.xml 리소스 확인 완료

app/src/main/res/xml/network_security_config.xml이 PR에 포함되어 있어 빌드 실패 우려가 없습니다.

app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (1)

87-87: Good: null-safe 처리

location 필드에 ?: "" 처리가 잘 되어 있네요. 다른 Fragment들과 달리 안전하게 처리되었습니다.

app/src/main/java/com/example/findu/data/mapper/todomain/SearchResponseDtoMapper.kt (3)

11-17: lastId의 null 처리 로직을 확인해주세요.

lastId가 null일 때 -1로 폴백하는 로직이 있는데, 페이지네이션에서 -1이 "마지막 페이지" 또는 "초기 상태"를 의미하는지 명확한지 확인이 필요합니다. 만약 -1이 특별한 의미를 가진다면, 주석이나 상수로 의도를 명확히 하는 게 좋을 것 같아요.

예시:

companion object {
    const val LAST_PAGE_ID = -1
}

// 사용처
lastId = this.lastId ?: LAST_PAGE_ID

40-47: LGTM!

SearchStatusSearchRvTag로 매핑하는 로직이 명확하고 모든 케이스를 완전하게 처리하고 있습니다.


1-47: 전체적으로 좋은 리팩토링입니다!

v2 API로의 마이그레이션이 깔끔하게 진행되고 있네요. DTO에서 Domain으로의 매핑이 명확하고, 코드 구조도 읽기 쉽습니다. 몇 가지 개선 제안을 드렸지만, 전반적으로 좋은 작업입니다.

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

Caution

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

⚠️ Outside diff range comments (1)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (1)

171-195: 북마크 Flow 수집이 두 번 등록되고 있어요.

onViewCreated()에서 이미 initBookmarkUI()를 호출한 뒤, initListener() 안에서 다시 호출하면서 launchWhenStarted 콜렉터가 중복으로 붙습니다. 동일한 emit마다 UI 업데이트가 두 번 실행되고, 재호출될 때마다 누적될 수 있으니 initListener() 쪽 중복 호출을 제거해 주세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ad1bdb and a39f83f.

📒 Files selected for processing (8)
  • app/src/main/java/com/example/findu/presentation/ui/search/SearchFilterFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (8 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/viewmodel/SearchFilterViewModel.kt (1 hunks)
  • app/src/main/res/drawable/bg_radius_20.xml (1 hunks)
  • app/src/main/res/layout/fragment_search_detail_disappear.xml (3 hunks)
  • app/src/main/res/layout/item_search_detail_img.xml (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (1)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (5)
  • initTagView (209-216)
  • initViewPager (140-157)
  • setupMap (81-90)
  • copyToClipboard (188-193)
  • updateBookmarkUI (246-251)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (7)
  • initListener (171-195)
  • initTagView (227-234)
  • initViewPager (152-169)
  • setupMap (88-97)
  • openNaverMap (236-261)
  • copyToClipboard (197-202)
  • updateBookmarkUI (264-269)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (7)
  • initListener (154-178)
  • initTagView (200-207)
  • initViewPager (135-152)
  • setupMap (80-89)
  • openNaverMap (209-234)
  • copyToClipboard (180-185)
  • updateBookmarkUI (236-241)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (4)
  • initTagView (209-216)
  • initViewPager (140-157)
  • setupMap (81-90)
  • copyToClipboard (188-193)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (4)
  • initTagView (200-207)
  • initViewPager (135-152)
  • setupMap (80-89)
  • copyToClipboard (180-185)
app/src/main/java/com/example/findu/presentation/ui/search/SearchFilterFragment.kt (4)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (1)
  • observeViewModel (103-117)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (1)
  • observeViewModel (110-125)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (1)
  • observeViewModel (101-115)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (1)
  • observeViewModel (58-79)

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

♻️ Duplicate comments (4)
app/src/main/java/com/example/findu/presentation/ui/search/SearchFilterFragment.kt (1)

90-91: 빈 품종 응답 처리 누락

breeds가 빈 리스트일 때 아무 것도 하지 않아 이전 품종 어댑터와 선택 상태가 남습니다. API가 빈 결과를 주면 UI가 오래된 데이터를 보여주니, 비었을 때는 선택/어댑터/힌트를 모두 초기화해 주세요.

-            filterViewModel.breedList.collectLatest { breeds ->
-                if (breeds.isNotEmpty()) setBreedData(breeds)
-            }
+            filterViewModel.breedList.collectLatest { breeds ->
+                if (breeds.isNotEmpty()) {
+                    setBreedData(breeds)
+                } else {
+                    selectedBreedList.clear()
+                    updateBreedChipsAndCounter()
+                    clearBreedAdapter()
+                    showBreedHintAndClear()
+                    setBreedFieldEnabled(false)
+                }
+            }
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (1)

54-57: viewLifecycleOwner 접근 시점 오류

onCreateView에서 observeViewModel()을 호출하면 내부에서 viewLifecycleOwner를 쓰는 순간 Fragment view lifecycle owner has not been set yet 예외가 터집니다. 호출을 onViewCreated 이후로 옮겨 주세요.

-        initRVAdapter()
-        observeViewModel()
+        initRVAdapter()
         setupRV(items)
@@
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+        observeViewModel()
         observeFilterResult()
         viewModel.getSearchData(SearchType.PROTECTING, lastProtectId)
     }
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (1)

52-55: viewLifecycleOwner 접근 시점 수정 필요

observeViewModel()viewLifecycleOwner를 참조하는데 onCreateView에서 바로 부르면 아직 초기화 전이라 크래시가 납니다. onViewCreated에서 호출하도록 이동해 주세요.

-        initRVAdapter()
-        observeViewModel()
+        initRVAdapter()
         return binding.root
@@
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+        observeViewModel()
         observeFilterResult()
         viewModel.getSearchData(SearchType.ALL, lastId)
     }
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (1)

55-58: viewLifecycleOwner 접근 시점 오류

onCreateView에서 observeViewModel()을 호출하면 내부에서 viewLifecycleOwner를 쓰는 순간 예외가 발생합니다. 호출 시점을 onViewCreated로 옮겨 주세요.

-        initRVAdapter()
-        observeViewModel()
+        initRVAdapter()
         setupRV(items)
@@
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+        observeViewModel()
         observeFilterResult()
         viewModel.getSearchData(SearchType.REPORTING, lastReportId)
     }
🧹 Nitpick comments (3)
app/src/main/java/com/example/findu/presentation/ui/search/model/SearchFilterUiModel.kt (1)

15-29: UI-to-Domain 변환 로직이 올바르게 구현되었습니다

toDomain() 함수가 SearchFilterUiModelSearchFilterData로 잘 변환하고 있습니다:

  • Species 매핑이 올바릅니다 (DOG/CAT/ETC → 한글 종명)
  • Null 처리도 적절합니다 (else 브랜치에서 null 반환)
  • 나머지 필드들도 정확하게 매핑되어 있습니다

원하신다면 species 매핑을 조금 더 간결하게 표현할 수도 있습니다:

-fun SearchFilterUiModel.toDomain(): SearchFilterData {
-    val koreanSpecies = when (this.species) {
-        SpeciesType.DOG.name -> SpeciesType.DOG.species
-        SpeciesType.CAT.name -> SpeciesType.CAT.species
-        SpeciesType.ETC.name -> SpeciesType.ETC.species
-        else -> null
-    }
+fun SearchFilterUiModel.toDomain(): SearchFilterData {
+    val koreanSpecies = SpeciesType.entries.find { it.name == this.species }?.species
     return SearchFilterData(
         startDate = this.startDate,
         endDate = this.endDate,

다만 현재 코드도 명확하고 읽기 쉬우니, 변경은 선택사항입니다.

app/src/main/java/com/example/findu/presentation/ui/search/viewmodel/SearchViewModel.kt (2)

74-77: enum을 String으로 변환하는 것보다 type-safe한 접근 고려

type.name을 사용해서 enum을 문자열로 변환하고 있는데, 이건 좀 위험할 수 있어요. enum이 리팩토링되거나 이름이 바뀌면 런타임에 문제가 생길 수 있거든요.

getReportsSearchType enum을 직접 받거나, sealed class 같은 type-safe한 방식을 고려해보는 게 어떨까요?

예를 들어, use case를 이렇게 바꿀 수 있습니다:

getSearchUseCase.getReports(
    type = type, // String 대신 SearchType enum 직접 전달
    searchFilterData = filter,
    lastId = lastId
)

그리고 GetSearchUseCase에서 enum을 받아서 내부적으로 처리하는 방식으로요.


63-66: lastId 파라미터 검증 추가 고려

lastId 파라미터가 Long.MAX_VALUE를 기본값으로 사용하고 있는데, 음수나 0이 들어올 경우에 대한 검증이 없네요. 필요하다면 require 같은 걸로 validation을 추가하는 것도 좋을 것 같아요.

예시:

 fun getSearchData(
     type: SearchType,
     lastId: Long = Long.MAX_VALUE,
 ) {
+    require(lastId > 0) { "lastId must be positive" }
     viewModelScope.launch {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a39f83f and 5a120dd.

📒 Files selected for processing (9)
  • app/src/main/java/com/example/findu/presentation/ui/search/BundleTag.kt (0 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/SearchFilterBottomSheet.kt (0 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/SearchFilterFragment.kt (6 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/model/SearchFilterUiModel.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/model/SearchType.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (4 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (4 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (4 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/viewmodel/SearchViewModel.kt (3 hunks)
💤 Files with no reviewable changes (2)
  • app/src/main/java/com/example/findu/presentation/ui/search/BundleTag.kt
  • app/src/main/java/com/example/findu/presentation/ui/search/SearchFilterBottomSheet.kt
🧰 Additional context used
🧬 Code graph analysis (4)
app/src/main/java/com/example/findu/presentation/ui/search/SearchFilterFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (1)
  • observeViewModel (110-125)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (1)
  • observeViewModel (87-108)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (3)
app/src/main/java/com/example/findu/presentation/ui/search/SearchFilterFragment.kt (1)
  • observeViewModel (87-99)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (4)
  • observeViewModel (87-108)
  • setupRV (110-131)
  • observeFilterResult (64-84)
  • navigateToDetail (133-160)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (4)
  • observeViewModel (89-110)
  • setupRV (112-133)
  • observeFilterResult (67-87)
  • navigateToDetail (135-162)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (5)
  • initRVAdapter (171-200)
  • observeViewModel (90-111)
  • observeFilterResult (68-88)
  • setupRV (113-134)
  • navigateToDetail (136-163)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (5)
  • initRVAdapter (168-197)
  • observeViewModel (89-110)
  • observeFilterResult (67-87)
  • setupRV (112-133)
  • navigateToDetail (135-162)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (4)
  • observeViewModel (87-108)
  • setupRV (110-131)
  • observeFilterResult (64-84)
  • navigateToDetail (133-160)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (4)
  • observeViewModel (90-111)
  • setupRV (113-134)
  • observeFilterResult (68-88)
  • navigateToDetail (136-163)
🔇 Additional comments (5)
app/src/main/java/com/example/findu/presentation/ui/search/model/SearchFilterUiModel.kt (1)

3-4: 새로운 도메인 매핑을 위한 import 추가 확인

UI 모델에서 도메인 모델로의 변환을 위해 SpeciesTypeSearchFilterData를 import하셨네요. 깔끔합니다!

app/src/main/java/com/example/findu/presentation/ui/search/viewmodel/SearchViewModel.kt (4)

16-17: 새로운 통합 검색 흐름을 위한 import 추가 확인됨

SearchType enum과 toDomain 확장 함수 import가 통합된 검색 API 구조를 지원하고 있네요. 깔끔합니다!


86-88: 에러 처리 확인됨

에러 메시지에 대한 fallback 처리가 잘 되어 있고, 한국어 메시지도 적절하네요.


63-91: 통합 검색 함수로 리팩토링한 접근이 좋네요

여러 개의 개별 fetch 메서드를 하나의 getSearchData 함수로 통합한 건 코드 중복을 줄이고 유지보수성을 높이는 좋은 개선이에요. when 표현식으로 타입별 처리를 깔끔하게 구분한 것도 마음에 드네요!


68-72: null 필터 처리 문제 없음

SearchRemoteDataSourceImpl에서 null인 searchFilterData는 각 @query 파라미터에 null로 전달되어 Retrofit이 해당 파라미터를 요청에서 생략하므로, filter가 null일 때에도 안전하게 동작합니다.

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

♻️ Duplicate comments (2)
app/src/main/java/com/example/findu/presentation/ui/search/SearchFilterFragment.kt (1)

82-86: 빈 품종 응답을 처리하지 않아 이전 데이터가 유지됩니다.

API가 빈 리스트를 반환하면 기존 어댑터와 선택 상태가 그대로 남아 다른 종을 선택했을 때 이전 품종이 계속 보입니다. 빈 결과일 때도 UI를 초기화해 주세요.

다음 diff를 적용하여 빈 응답 시 초기화를 추가하세요:

         lifecycleScope.launchWhenStarted {
             filterViewModel.breedList.collectLatest { breeds ->
-                if (breeds.isNotEmpty()) setBreedData(breeds)
+                if (breeds.isNotEmpty()) {
+                    setBreedData(breeds)
+                } else {
+                    selectedBreedList.clear()
+                    updateBreedChipsAndCounter()
+                    clearBreedAdapter()
+                    showBreedHintAndClear()
+                    setBreedFieldEnabled(false)
+                }
             }
         }
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (1)

48-62: [중요] 여전히 해결되지 않은 크래시 위험

이전 리뷰에서 지적된 문제가 아직 수정되지 않았어요. onCreateView에서 observeViewModel()을 호출하면 viewLifecycleOwner가 아직 초기화되지 않아서 IllegalStateException이 발생합니다 (line 88, 101에서 viewLifecycleOwner.lifecycleScope 접근 시).

observeViewModel() 호출을 onViewCreated로 옮겨주세요.

 override fun onCreateView(
     inflater: LayoutInflater, container: ViewGroup?,
     savedInstanceState: Bundle?,
 ): View {
     _binding = FragmentSearchAllBinding.inflate(inflater, container, false)
     initRVAdapter()
-    observeViewModel()
     return binding.root
 }

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     super.onViewCreated(view, savedInstanceState)
+    observeViewModel()
     observeFilterResult()
     viewModel.getSearchData(SearchType.ALL, lastId)
 }
🧹 Nitpick comments (3)
app/src/main/res/layout/search_horizontal_content_item.xml (1)

45-48: 이름 텍스트 잘림 시 시각적 힌트 추가 권장

maxLength="10"만 적용하면 긴 이름이 10자에서 갑자기 끊겨 보여 사용자 경험이 어색해질 수 있습니다. android:ellipsize="end"android:maxLines="1"을 함께 설정해 말줄임표로 표시되도록 보완하는 것을 권장드립니다.

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

82-92: launchWhenStarted는 더 이상 권장되지 않습니다.

launchWhenStarted 대신 repeatOnLifecycle(STARTED)를 사용하면 lifecycle state가 STARTED 미만일 때 코루틴을 자동으로 취소하고 재시작합니다. 이렇게 하면 background에서 불필요한 수집을 방지할 수 있습니다.

다음과 같이 수정하세요:

     private fun observeViewModel() {
-        lifecycleScope.launchWhenStarted {
+        viewLifecycleOwner.lifecycleScope.launch {
+            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
-            filterViewModel.breedList.collectLatest { breeds ->
-                if (breeds.isNotEmpty()) setBreedData(breeds)
-            }
-        }
+                filterViewModel.breedList.collectLatest { breeds ->
+                    if (breeds.isNotEmpty()) setBreedData(breeds)
+                }
+            }
+        }
 
-        lifecycleScope.launchWhenStarted {
+        viewLifecycleOwner.lifecycleScope.launch {
+            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
-            filterViewModel.errorMessage.collectLatest { msg ->
-                msg?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show() }
-            }
-        }
+                filterViewModel.errorMessage.collectLatest { msg ->
+                    msg?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show() }
+                }
+            }
+        }
     }

androidx.lifecycle:lifecycle-runtime-ktx dependency가 필요합니다.

app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (1)

75-78: 50ms 딜레이의 의도를 확인해주세요

데이터 페칭 전에 50ms 딜레이가 있는데, 이게 상태 업데이트 완료를 보장하기 위한 건지 궁금하네요. 다른 탭 프래그먼트들(SearchReportFragment, SearchRescueFragment)도 동일한 패턴을 사용하고 있어서 의도적인 것 같긴 한데, 가능하면 타이밍 의존성 없이 명시적인 상태 동기화 방법을 고려해보는 게 좋을 것 같아요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a120dd and ba294eb.

📒 Files selected for processing (6)
  • app/src/main/java/com/example/findu/presentation/ui/search/SearchFilterFragment.kt (7 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/adapter/SearchListAdapter.kt (7 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (4 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/viewmodel/SearchViewModel.kt (3 hunks)
  • app/src/main/res/drawable/ic_search_blank_bookmark_horizontal.xml (1 hunks)
  • app/src/main/res/layout/search_horizontal_content_item.xml (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/java/com/example/findu/presentation/ui/search/adapter/SearchListAdapter.kt
  • app/src/main/java/com/example/findu/presentation/ui/search/viewmodel/SearchViewModel.kt
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/com/example/findu/presentation/ui/search/SearchFilterFragment.kt (3)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (1)
  • observeViewModel (103-117)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (1)
  • observeViewModel (110-125)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (1)
  • observeViewModel (87-108)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (5)
  • initRVAdapter (171-200)
  • observeViewModel (90-111)
  • observeFilterResult (68-88)
  • setupRV (113-134)
  • navigateToDetail (136-163)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (5)
  • initRVAdapter (168-197)
  • observeViewModel (89-110)
  • observeFilterResult (67-87)
  • setupRV (112-133)
  • navigateToDetail (135-162)
🔇 Additional comments (5)
app/src/main/java/com/example/findu/presentation/ui/search/SearchFilterFragment.kt (2)

179-192: 품종 로딩이 잘 적용되었습니다.

각 종 선택 시 ViewModel을 통해 동적으로 품종 데이터를 불러오도록 변경한 것은 좋은 접근입니다.


464-470: Navigation SavedStateHandle 전환이 올바르게 적용되었습니다.

FragmentResult에서 Navigation의 SavedStateHandle로 데이터 전달 방식을 변경한 것은 Navigation Component의 권장 패턴을 따르는 좋은 개선입니다.

app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (3)

87-108: 데이터 관찰 로직 잘 구현되었어요

allSearchDataerrorMessage를 제대로 관찰하고 있고, 빈 결과 처리도 적절하게 되어 있네요. 다른 탭 프래그먼트들과 일관된 패턴을 따르고 있어서 좋습니다.


110-131: UI 모델 매핑 잘 되어있네요

null-safe 처리가 제대로 되어있고 (line 113, 116), reportId 사용도 다른 프래그먼트들과 일관성 있게 구현되어 있습니다. 새 리스트일 때 스크롤 위치 초기화도 적절해요.


166-199: 어댑터 초기화 및 페이지네이션 로직 깔끔해요

reportId를 사용한 네비게이션 (line 170)과 SearchType.ALL 기반 페이지네이션 (line 195)이 다른 탭 프래그먼트들과 일관되게 구현되어 있습니다.

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

♻️ Duplicate comments (3)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (1)

56-66: onCreateView에서는 viewLifecycleOwner를 사용할 수 없습니다

Line 57에서 observeViewModel()을 호출하면 내부에서 viewLifecycleOwner.lifecycleScope 접근 시점이 너무 빨라서 IllegalStateException이 재발합니다. 이전 리뷰와 동일한 이슈가 아직 남아 있으니, 호출을 onViewCreated 이후로 옮겨 주세요. 대표적인 수정 예시는 아래와 같습니다.

@@
-        initRVAdapter()
-        observeViewModel()
-        setupRV(items)
+        initRVAdapter()
+        setupRV(items)
@@
-        observeFilterResult()
-        viewModel.getSearchData(SearchType.PROTECTING, lastProtectId)
+        observeViewModel()
+        observeFilterResult()
+        viewModel.getSearchData(SearchType.PROTECTING, lastProtectId)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (1)

56-67: onCreateView에서는 viewLifecycleOwner를 사용할 수 없습니다

Line 58에서 observeViewModel()을 호출하면 viewLifecycleOwner.lifecycleScope 접근 때문에 IllegalStateException이 다시 발생합니다. onViewCreated에서 호출하도록 이동해 주세요. 예시는 다음과 같습니다.

@@
-        initRVAdapter()
-        observeViewModel()
-        setupRV(items)
+        initRVAdapter()
+        setupRV(items)
@@
-        observeFilterResult()
-        viewModel.getSearchData(SearchType.REPORTING, lastReportId)
+        observeViewModel()
+        observeFilterResult()
+        viewModel.getSearchData(SearchType.REPORTING, lastReportId)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (1)

48-56: 크래시: 이전 리뷰 이슈가 여전히 해결 안 됨

onCreateView에서 observeViewModel()을 호출하고 있는데, 이 함수는 viewLifecycleOwner.lifecycleScope를 사용합니다(라인 88, 101). 문제는 viewLifecycleOwneronCreateView 시점에는 아직 초기화되지 않아서 IllegalStateException이 발생한다는 점이에요. 이전 리뷰에서도 똑같은 이슈가 지적됐었는데 아직 수정이 안 됐네요.

다음과 같이 수정해 주세요:

 override fun onCreateView(
     inflater: LayoutInflater, container: ViewGroup?,
     savedInstanceState: Bundle?,
 ): View {
     _binding = FragmentSearchAllBinding.inflate(inflater, container, false)
     initRVAdapter()
-    observeViewModel()
     return binding.root
 }
 
 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     super.onViewCreated(view, savedInstanceState)
+    observeViewModel()
     observeFilterResult()
     viewModel.getSearchData(SearchType.ALL, lastId)
 }
🧹 Nitpick comments (1)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (1)

75-77: 매직 넘버 대신 상수로 빼는 게 어떨까요?

delay(50) 부분이 좀 애매한데, UI 업데이트나 상태 동기화를 위한 것 같긴 한데 나중에 코드 보는 사람이 "왜 50ms지?"라고 궁금해할 수 있어요.

이렇게 상수로 빼면 더 명확해질 것 같아요:

companion object {
    private const val FILTER_UPDATE_DELAY_MS = 50L
}

// 사용처
viewLifecycleOwner.lifecycleScope.launch {
    delay(FILTER_UPDATE_DELAY_MS)
    viewModel.getSearchData(SearchType.ALL, lastId)
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba294eb and 78f0292.

📒 Files selected for processing (6)
  • app/src/main/java/com/example/findu/presentation/ui/search/adapter/SearchListAdapter.kt (4 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/adapter/SearchListListener.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (4 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (5 hunks)
  • app/src/main/res/navigation/nav_graph.xml (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (5)
  • observeViewModel (87-108)
  • setupRV (110-131)
  • observeFilterResult (64-84)
  • toggleLayoutMode (193-215)
  • navigateToDetail (133-160)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (5)
  • observeViewModel (91-112)
  • setupRV (114-135)
  • observeFilterResult (69-89)
  • toggleLayoutMode (197-219)
  • navigateToDetail (137-164)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (1)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (6)
  • observeViewModel (87-108)
  • setupRV (110-131)
  • observeFilterResult (64-84)
  • navigateToFilter (162-164)
  • toggleLayoutMode (193-215)
  • navigateToDetail (133-160)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (1)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (7)
  • initRVAdapter (172-195)
  • observeViewModel (91-112)
  • observeFilterResult (69-89)
  • setupRV (114-135)
  • navigateToFilter (167-169)
  • toggleLayoutMode (197-219)
  • navigateToDetail (137-164)
🔇 Additional comments (3)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (3)

113-116: 널 안전성 처리 좋습니다

thumbnailImageUrllocation에 Elvis 연산자로 빈 문자열 기본값 처리한 거 깔끔하네요. Glide가 빈 문자열도 잘 처리하니까 크래시 걱정 없을 것 같아요.


167-174: 어댑터 설정 적절합니다

리스너 패턴 적용하고 setHasFixedSize(true), itemAnimator = null 설정해서 성능 최적화한 거 좋네요. 리스트 업데이트 시 깜빡임도 줄어들 겁니다.


223-234: 리스너 구현 깔끔하게 되었습니다

각 메서드가 적절한 곳으로 잘 위임되고 있고, 특히 onBookmarkClick에서 tag 파라미터까지 제대로 전달하는 거 좋아요. 코드가 간결하고 읽기 쉽습니다.

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

♻️ Duplicate comments (3)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (1)

56-57: 🔴 이전 리뷰에서 지적된 크리티컬 이슈가 아직 수정되지 않았습니다

onCreateView에서 observeViewModel()을 호출하면 내부에서 viewLifecycleOwner.lifecycleScope(91, 104번 라인)에 접근하려다가 IllegalStateException이 발생합니다. 이 문제는 이전 리뷰에서도 지적되었으나 아직 수정되지 않았습니다.

다음 diff를 적용하여 onViewCreated로 호출 시점을 옮겨주세요:

 override fun onCreateView(
     inflater: LayoutInflater, container: ViewGroup?,
     savedInstanceState: Bundle?,
 ): View {
     _binding = FragmentSearchRescueBinding.inflate(inflater, container, false)
     initRVAdapter()
-    observeViewModel()
-    setupRV(items)
-
     return binding.root
 }

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     super.onViewCreated(view, savedInstanceState)
+    observeViewModel()
     observeFilterResult()
     viewModel.getSearchData(SearchType.PROTECTING, lastProtectId)
 }
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (1)

54-54: 🔴 이전 리뷰에서 지적된 크리티컬 이슈가 아직 수정되지 않았습니다

onCreateView에서 observeViewModel()을 호출하면 내부에서 viewLifecycleOwner.lifecycleScope(88, 101번 라인)에 접근하려다가 IllegalStateException이 발생합니다. 이 문제는 이전 리뷰에서도 지적되었으나 아직 수정되지 않았습니다.

다음 diff를 적용하여 onViewCreated로 호출 시점을 옮겨주세요:

 override fun onCreateView(
     inflater: LayoutInflater, container: ViewGroup?,
     savedInstanceState: Bundle?,
 ): View {
     _binding = FragmentSearchAllBinding.inflate(inflater, container, false)
     initRVAdapter()
-    observeViewModel()
     return binding.root
 }

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     super.onViewCreated(view, savedInstanceState)
+    observeViewModel()
     observeFilterResult()
     viewModel.getSearchData(SearchType.ALL, lastId)
 }
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (1)

58-59: 🔴 이전 리뷰에서 수정되었다고 표시되었으나 여전히 동일한 크리티컬 이슈가 존재합니다

이전 리뷰에서 이 문제가 커밋 5a120dd에서 해결되었다고 표시되었지만, onCreateView에서 observeViewModel()을 호출하면 내부에서 viewLifecycleOwner.lifecycleScope(92, 105번 라인)에 접근하려다가 IllegalStateException이 여전히 발생합니다.

다음 diff를 적용하여 onViewCreated로 호출 시점을 옮겨주세요:

 override fun onCreateView(
     inflater: LayoutInflater, container: ViewGroup?,
     savedInstanceState: Bundle?,
 ): View {
     _binding = FragmentSearchReportBinding.inflate(inflater, container, false)
     initRVAdapter()
-    observeViewModel()
-    setupRV(items)
     return binding.root
 }

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     super.onViewCreated(view, savedInstanceState)
+    observeViewModel()
     observeFilterResult()
     viewModel.getSearchData(SearchType.REPORTING, lastReportId)
 }
🧹 Nitpick comments (4)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (3)

48-48: 사용되지 않는 변수 제거 권장

items 변수는 선언 후 setupRV(items)에서 빈 리스트로만 사용되고 이후 업데이트되지 않습니다.

다음 diff를 적용하여 제거하고 직접 빈 리스트를 전달하는 것을 고려해보세요:

-    private var items = ArrayList<SearchAnimal>()
-
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?,
     ): View {
         _binding = FragmentSearchRescueBinding.inflate(inflater, container, false)
         initRVAdapter()
-        observeViewModel()
-        setupRV(items)
-
         return binding.root
     }

68-88: 매직 넘버와 코드 중복 개선 고려

  • delay(50) 매직 넘버는 상수로 추출하는 것이 좋습니다.
  • observeFilterResult(), observeViewModel(), setupRV() 등의 메서드가 SearchAllFragment, SearchReportFragment와 거의 동일하게 중복됩니다. 공통 베이스 클래스나 공통 함수로 추출하여 DRY 원칙을 준수하는 것을 권장합니다.

매직 넘버를 상수로 추출하는 예시:

+    companion object {
+        private const val FILTER_UPDATE_DELAY_MS = 50L
+    }
+
     private fun observeFilterResult() {
         findNavController().currentBackStackEntry
             ?.savedStateHandle
             ?.getLiveData<SearchFilterUiModel>(SELECTED_FILTER_DATA)
             ?.observe(viewLifecycleOwner) { selected ->
                 Log.d("SearchFilter", "필터 수신됨: $selected")

                 viewModel.updateProtectFilterState(selected)
                 isNewList = true
                 lastProtectId = Long.MAX_VALUE

                 viewLifecycleOwner.lifecycleScope.launch {
-                    delay(50)
+                    delay(FILTER_UPDATE_DELAY_MS)
                     viewModel.getSearchData(SearchType.PROTECTING, lastProtectId)
                 }

119-119: location null 처리 일관화
SearchAnimal.location은 non-nullable String으로 정의되어 null이 발생하지 않습니다. SearchAllFragmentitem.location ?: ""는 불필요하니 제거하거나, SearchRescueFragment에도 ?: ""를 추가해 두 Fragment의 할당 방식을 통일하세요.

app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (1)

49-49: 사용되지 않는 변수 제거 권장

items 변수는 선언 후 setupRV(items)에서 빈 리스트로만 사용되고 이후 업데이트되지 않습니다.

다음 diff를 적용하여 제거하는 것을 고려해보세요:

-    private var items = ArrayList<SearchAnimal>()
-

     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?,
     ): View {
         _binding = FragmentSearchReportBinding.inflate(inflater, container, false)
         initRVAdapter()
-        observeViewModel()
-        setupRV(items)
         return binding.root
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78f0292 and a2c0bee.

📒 Files selected for processing (11)
  • app/src/main/java/com/example/findu/presentation/ui/search/SearchFilterFragment.kt (0 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchBreedSelector.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchCalendarSelector.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchFilterFragment.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchLocationSelector.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchSpeciesSelector.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (4 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (5 hunks)
  • app/src/main/res/layout/fragment_search_filter.xml (1 hunks)
  • app/src/main/res/navigation/nav_graph.xml (2 hunks)
💤 Files with no reviewable changes (1)
  • app/src/main/java/com/example/findu/presentation/ui/search/SearchFilterFragment.kt
🧰 Additional context used
🧬 Code graph analysis (4)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (1)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (5)
  • observeViewModel (87-108)
  • setupRV (110-131)
  • observeFilterResult (64-84)
  • navigateToFilter (162-166)
  • navigateToDetail (133-160)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchFilterFragment.kt (4)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchBreedSelector.kt (1)
  • init (26-57)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchCalendarSelector.kt (1)
  • init (20-54)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchLocationSelector.kt (1)
  • init (20-59)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchSpeciesSelector.kt (1)
  • init (16-64)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (1)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (6)
  • observeViewModel (87-108)
  • setupRV (110-131)
  • observeFilterResult (64-84)
  • navigateToFilter (162-166)
  • toggleLayoutMode (194-216)
  • navigateToDetail (133-160)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (5)
  • initRVAdapter (173-196)
  • observeViewModel (91-112)
  • observeFilterResult (69-89)
  • setupRV (114-135)
  • navigateToDetail (137-164)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (5)
  • initRVAdapter (171-197)
  • observeViewModel (90-111)
  • observeFilterResult (68-88)
  • setupRV (113-134)
  • navigateToDetail (136-163)

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

♻️ Duplicate comments (2)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchBreedSelector.kt (1)

76-94: 이전 품종 선택이 남아있어 필터가 꼬일 수 있습니다.

setBreeds(breeds)가 빈 리스트를 받으면 기존 선택(selectedList)과 칩 UI, 활성화 상태가 그대로 유지되어 잘못된 필터가 전송될 수 있습니다. 또한 품종 리스트가 바뀌었을 때 이전 종의 품종이 selectedList에 남아있으면 종/품종 조합이 섞입니다.

다음 diff를 적용해 주세요:

 fun setBreeds(breeds: List<String>) = with(binding) {
     if (breeds.isEmpty()) {
+        selectedList.clear()
+        updateChips()
+        setBreedFieldEnabled(false)
         flFilterBreedContainer.isGone = true
+        adapter = null
         return@with
     }
 
+    selectedList.retainAll(breeds)
+    updateChips()
+
     adapter = SearchBreedRVAdapter(
         allItems = breeds,
         isSelected = { selectedList.contains(it) },
         onPick = { picked ->
             if (picked in selectedList) removeBreed(picked) else addBreed(picked)
             adapter?.refreshSelections()
             toggleDropdown(true)
         }
     )
 
     rvSearchFilterBreed.adapter = adapter
     adapter?.filter?.filter(token())
 }
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchFilterFragment.kt (1)

50-58: 초기화가 두 번 실행되어 리스너가 중복 등록됩니다.

breedSelector를 생성하면서 .apply { init() }로 한 번 초기화하고(51번 줄), 다시 breedSelector.init()을 호출(56번 줄)하여 TextWatcher와 클릭 리스너가 두 번 등록됩니다. 이로 인해 텍스트 변경이나 클릭 이벤트가 중복 실행될 수 있습니다.

다음 diff를 적용해 주세요:

-    breedSelector = SearchBreedSelector(binding).apply { init() }
+    breedSelector = SearchBreedSelector(binding)
     locationSelector = SearchLocationSelector(binding)
     calendarSelector = SearchCalendarSelector(binding, filterModel, parentFragmentManager)
 
     speciesSelector.init()
     breedSelector.init()
     locationSelector.init()
     calendarSelector.init()
🧹 Nitpick comments (3)
app/src/main/java/com/example/findu/presentation/ui/search/viewmodel/SearchViewModel.kt (1)

93-103: 필터 업데이트 로직을 약간 단순화할 수 있습니다.

현재 filterUiModel?.copy()를 사용하고 있는데, filterUiModelnull일 때는 copy()를 호출하지 않고 직접 null을 할당하는 것이 더 명확합니다.

다음과 같이 리팩터링을 고려해 보세요:

 fun updateAllFilterState(filterUiModel: SearchFilterUiModel?) {
-    _allFilter = filterUiModel?.copy()
+    _allFilter = filterUiModel
 }
 
 fun updateReportFilterState(filterUiModel: SearchFilterUiModel?) {
-    _reportFilter = filterUiModel?.copy()
+    _reportFilter = filterUiModel
 }
 
 fun updateProtectFilterState(filterUiModel: SearchFilterUiModel?) {
-    _protectFilter = filterUiModel?.copy()
+    _protectFilter = filterUiModel
 }

또는 불변성을 위해 copy()를 유지하려면:

 fun updateAllFilterState(filterUiModel: SearchFilterUiModel?) {
-    _allFilter = filterUiModel?.copy()
+    _allFilter = filterUiModel?.copy() ?: null
 }
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchFilterFragment.kt (2)

64-88: 종과 품종 연동 로직이 잘 구성되어 있습니다.

종 선택 시 품종을 리셋하고 새 품종을 로드하는 흐름이 명확합니다. 다만 launchWhenStarted는 deprecated되었으니 향후 repeatOnLifecycle(Lifecycle.State.STARTED)로 마이그레이션을 고려해 보세요.

향후 다음과 같이 리팩터링을 고려해 보세요:

viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        filterViewModel.breedList.collectLatest { breeds ->
            if (breeds.isNotEmpty()) breedSelector.setBreeds(breeds)
        }
    }
}

96-119: 필터 리셋 로직에 코드 중복이 있습니다.

resetFilters()when 표현식에서 세 분기가 동일한 패턴을 반복하고 있습니다. 다음과 같이 리팩터링하면 더 간결해집니다.

private fun resetFilters() {
    filterModel.startDate = null
    filterModel.endDate = null

    speciesSelector.reset()
    breedSelector.reset()
    locationSelector.reset()
    calendarSelector.reset()

    val type = args.searchType
    searchViewModel.clearFilterState(type)
    searchViewModel.getSearchData(type)
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2c0bee and 08f2dc0.

📒 Files selected for processing (3)
  • app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchBreedSelector.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchFilterFragment.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/viewmodel/SearchViewModel.kt (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchFilterFragment.kt (4)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchBreedSelector.kt (1)
  • init (25-56)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchCalendarSelector.kt (1)
  • init (20-54)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchSpeciesSelector.kt (1)
  • init (16-64)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchLocationSelector.kt (1)
  • init (20-59)
🪛 detekt (1.23.8)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchBreedSelector.kt

[warning] 44-44: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)


[warning] 45-45: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)

🔇 Additional comments (5)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchBreedSelector.kt (2)

25-56: 초기화 로직이 잘 구성되어 있습니다.

RecyclerView 설정, 리스너 등록, TextWatcher 추가가 깔끔하게 처리되었습니다. 빈 beforeTextChangedafterTextChanged는 인터페이스 요구사항이므로 문제없습니다.


96-132: 품종 추가/제거 및 칩 UI 업데이트가 잘 구현되었습니다.

최대 10개 제한, 중복 체크, 칩 생성/제거, suppressWatcher를 통한 무한 루프 방지 등이 모두 적절하게 처리되어 있습니다.

app/src/main/java/com/example/findu/presentation/ui/search/viewmodel/SearchViewModel.kt (2)

63-91: 통합된 데이터 로딩 메서드가 잘 구성되었습니다.

타입별로 분리된 메서드들을 getSearchData(type: SearchType)로 통합하여 코드 중복을 제거했습니다. 타입에 따라 적절한 필터를 선택하고 결과를 해당 StateFlow에 업데이트하는 로직이 명확합니다.

API가 type.name (예: "REPORTING", "PROTECTING")을 그대로 받는지 확인해 주세요. 만약 API가 다른 형식(예: 소문자, 다른 enum 값)을 요구한다면 매핑이 필요할 수 있습니다.


105-111: 타입별 필터 초기화 메서드가 깔끔하게 추가되었습니다.

clearFilterState(type: SearchType)가 타입에 따라 적절한 필터를 초기화하고 있습니다.

app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchFilterFragment.kt (1)

121-160: 필터 적용 로직이 체계적으로 잘 구현되었습니다.

날짜 범위의 엣지 케이스(둘 중 하나만 선택)를 처리하고, 모든 필터가 비어있을 때 빈 모델을 반환하며, 타입별로 적절한 필터 상태를 업데이트한 뒤 savedStateHandle로 결과를 전달하는 흐름이 깔끔합니다.

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

Caution

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

⚠️ Outside diff range comments (1)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (1)

95-100: 태그 분기 오류: 목격신고가 실종 조회로 라우팅됨

이 프래그먼트는 실종 상세 전용으로 보입니다. "목격신고"는 목격 상세로 가야 합니다. 실종만 처리하도록 수정하세요.

적용 diff:

-        when (tag) {
-            "목격신고", "실종신고" -> viewModel.getDetailSearchMissing(cardId)
+        when (tag) {
+            "실종신고" -> viewModel.getDetailSearchMissing(cardId)
             else -> {
                 Toast.makeText(requireContext(), "잘못된 태그 값입니다.", Toast.LENGTH_SHORT).show()
                 requireActivity().supportFragmentManager.popBackStack()
             }
         }
♻️ Duplicate comments (3)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchBreedSelector.kt (1)

76-101: 이전에 지적된 문제가 해결되었어요.

setBreeds()가 빈 리스트를 받을 때 이제 selectedList.clear(), 칩 업데이트, 필드 비활성화를 모두 처리하고 있고, 비어있지 않은 경우에도 retainAll(breeds)로 이전 종에서 선택한 품종을 제거하고 있어요. 이전 리뷰에서 언급된 문제가 잘 수정되었네요!

app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (1)

143-144: 주소 바인딩 추가 확인

tvValueProtectLocation이 이제 데이터로 채워집니다. 이전 이슈가 해소되었어요.

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

135-160: 보호 관심 해제 성공 시 isInterested 갱신 누락

deleteInterestProtectingAnimalUseCase 성공 분기에서 _isInterested를 false로 설정하지 않아 UI가 동기화되지 않습니다.

적용 diff:

             } else {
                 deleteInterestProtectingAnimalUseCase(id).fold(
                     onSuccess = {
                         _detailProtectData.value = _detailProtectData.value?.copy(interest = false)
+                        _isInterested.value = false
                     },
                     onFailure = { error ->
                         _errorMessage.value = error.message ?: "보호 관심 해제 실패"
                     }
                 )
             }
🧹 Nitpick comments (11)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchFilterFragment.kt (2)

77-91: launchWhenStarted는 더 이상 권장되지 않아요.

launchWhenStarted는 deprecated된 API예요. repeatOnLifecycle(Lifecycle.State.STARTED)를 사용하는 게 더 안전하고 권장되는 방식이에요.

아래 diff를 적용해서 두 코루틴을 최신 API로 변경해주세요:

-        viewLifecycleOwner.lifecycleScope.launchWhenStarted {
+        viewLifecycleOwner.lifecycleScope.launch {
+            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
-            filterViewModel.breedList.collectLatest { breeds ->
-                if (breeds.isNotEmpty()) {
-                    breedSelector.setBreeds(breeds)
-                } else {
-                    breedSelector.reset()
-                }
+                filterViewModel.breedList.collectLatest { breeds ->
+                    if (breeds.isNotEmpty()) {
+                        breedSelector.setBreeds(breeds)
+                    } else {
+                        breedSelector.reset()
+                    }
+                }
             }
         }
 
-        viewLifecycleOwner.lifecycleScope.launchWhenStarted {
+        viewLifecycleOwner.lifecycleScope.launch {
+            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
-            filterViewModel.errorMessage.collectLatest { msg ->
-                msg?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show() }
+                filterViewModel.errorMessage.collectLatest { msg ->
+                    msg?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show() }
+                }
             }
         }

101-124: when 분기의 중복 로직을 정리할 수 있어요.

세 개의 SearchType 분기가 동일한 패턴으로 clearFilterState와 getSearchData를 호출하고 있어요. 이 로직을 간소화하면 코드가 더 깔끔해질 것 같아요.

다음처럼 리팩토링을 고려해보세요:

-        when (args.searchType) {
-            SearchType.ALL -> {
-                searchViewModel.clearFilterState(SearchType.ALL)
-                searchViewModel.getSearchData(SearchType.ALL)
-            }
-            SearchType.REPORTING -> {
-                searchViewModel.clearFilterState(SearchType.REPORTING)
-                searchViewModel.getSearchData(SearchType.REPORTING)
-            }
-            SearchType.PROTECTING -> {
-                searchViewModel.clearFilterState(SearchType.PROTECTING)
-                searchViewModel.getSearchData(SearchType.PROTECTING)
-            }
-        }
+        args.searchType.let { type ->
+            searchViewModel.clearFilterState(type)
+            searchViewModel.getSearchData(type)
+        }
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchBreedSelector.kt (1)

106-106: 하드코딩된 문자열을 리소스로 옮기는 게 좋을 것 같아요.

"최대 10개까지 선택할 수 있어요." 문자열이 하드코딩되어 있어요. 다국어 지원과 유지보수를 위해 string resource로 빼는 걸 고려해보세요.

예를 들어:

-            Toast.makeText(binding.root.context, "최대 10개까지 선택할 수 있어요.", Toast.LENGTH_SHORT).show()
+            Toast.makeText(binding.root.context, binding.root.context.getString(R.string.search_filter_breed_max_count), Toast.LENGTH_SHORT).show()
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (2)

67-87: 필터 적용 시 delay(50) 사용 이유를 명확히 해주세요.

필터 데이터를 받은 후 50ms 지연을 두고 데이터를 다시 요청하는 패턴이 보입니다. 이 지연이 특정 타이밍 이슈를 해결하기 위한 것이라면 주석으로 설명을 추가하거나, 더 명시적인 방법(예: StateFlow의 업데이트 완료 대기)을 고려해보면 좋을 것 같습니다.

참고로 SearchAllFragment와 SearchReportFragment에도 동일한 패턴이 있으니, 이 지연이 필요한 이유를 확인해주세요.


35-35: 세 Fragment 간 코드 중복 개선을 고려해보세요.

SearchRescueFragment, SearchAllFragment, SearchReportFragment가 거의 동일한 구조와 로직을 공유하고 있습니다. SearchType만 다를 뿐 나머지 코드가 매우 유사합니다.

시간이 될 때 다음과 같은 리팩토링을 고려해볼 수 있습니다:

  • Base Fragment 클래스를 만들어 공통 로직을 추출
  • 또는 Composition 패턴을 활용하여 중복 제거

현재 구현도 잘 동작하지만, 향후 유지보수성을 위해 고려해볼 만한 개선점입니다.

Also applies to: 67-110, 170-196, 229-240

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

32-39: 알 수 없는 태그 처리: 예외 대신 UNKNOWN으로 폴백 권장

서버/데이터가 확장되면 앱 크래시로 이어집니다. UI에서는 UNKNOWN 태그로 안전하게 표시하는 편이 낫습니다.

적용 diff:

-fun String.toSearchStatus(): SearchStatus {
-    return when (this.trim()) {
-        "보호중" -> SearchStatus.PROTECTING
-        "목격신고" -> SearchStatus.WITNESS
-        "실종신고" -> SearchStatus.MISSING
-        else -> throw IllegalArgumentException("Unknown tag value: $this")
-    }
-}
+fun String.toSearchStatus(): SearchStatus {
+    return when (this.trim()) {
+        "보호중" -> SearchStatus.PROTECTING
+        "목격신고" -> SearchStatus.WITNESS
+        "실종신고" -> SearchStatus.MISSING
+        else -> SearchStatus.UNKNOWN
+    }
+}
app/src/main/java/com/example/findu/data/repositoryimpl/SearchRepositoryImpl.kt (1)

15-16: type를 문자열로 전달하는 대신 타입 세이프 구조 권장

Enum/Sealed class로 타입을 제한하면 오타/변경에 안전합니다.

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

68-75: 알 수 없는 태그 예외 대신 UNKNOWN 권장

UI 처리 관점에선 크래시보다 안전 폴백이 낫습니다. toSearchStatus와 로직도 중복되니 공통 유틸로 통합 고려 바랍니다.

적용 diff:

 fun String.toDetailSearchStatus(): SearchStatus {
-    return when (this) {
+    return when (this) {
         "보호중" -> SearchStatus.PROTECTING
         "목격신고" -> SearchStatus.WITNESS
         "실종신고" -> SearchStatus.MISSING
-        else -> throw IllegalArgumentException("Unknown tag value: $this")
+        else -> SearchStatus.UNKNOWN
     }
 }
app/src/main/java/com/example/findu/data/repositoryimpl/DetailSearchRepositoryImpl.kt (1)

24-40: 실종/목격 상세 조회 추가(LGTM)

중복 패턴이지만 명확합니다. 필요 시 private 헬퍼로 중복 제거 가능.

app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (1)

80-88: 지도 준비 타이밍 보완 제안

naverMap이 아직 준비되지 않은 타이밍에 setupMap이 호출되면 위치 반영이 누락될 수 있습니다. 보호 상세처럼 pendingLocation(또는 큐)로 버퍼링하는 패턴을 적용하는 걸 권장합니다.

app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (1)

81-89: 지도 준비 타이밍 보완 제안

Witness/Disappear 모두 보호 상세처럼 pendingLocation 패턴 도입을 권장합니다. 초기 표시 누락을 방지합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08f2dc0 and 35efaac.

📒 Files selected for processing (17)
  • app/src/main/java/com/example/findu/data/mapper/todomain/DetailSearchResponseDtoMapper.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/mapper/todomain/LoginResponseDtoMapper.kt (0 hunks)
  • app/src/main/java/com/example/findu/data/mapper/todomain/SearchResponseDtoMapper.kt (2 hunks)
  • app/src/main/java/com/example/findu/data/repositoryimpl/DetailSearchRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/example/findu/data/repositoryimpl/ReportRepositoryImpl.kt (0 hunks)
  • app/src/main/java/com/example/findu/data/repositoryimpl/SearchRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/mapper/torvmodel/SearchFilterToDomainMapper.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/my/MyFragment.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (8 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchBreedSelector.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchFilterFragment.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (6 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (6 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/viewmodel/DetailSearchViewModel.kt (2 hunks)
💤 Files with no reviewable changes (2)
  • app/src/main/java/com/example/findu/data/mapper/todomain/LoginResponseDtoMapper.kt
  • app/src/main/java/com/example/findu/data/repositoryimpl/ReportRepositoryImpl.kt
🧰 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/SearchResponseDtoMapper.kt
  • app/src/main/java/com/example/findu/data/mapper/todomain/DetailSearchResponseDtoMapper.kt
🧬 Code graph analysis (6)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (6)
  • observeViewModel (85-106)
  • observeFilterResult (62-82)
  • setupRV (108-129)
  • navigateToFilter (160-164)
  • toggleLayoutMode (192-214)
  • navigateToDetail (131-158)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (6)
  • observeViewModel (89-110)
  • observeFilterResult (67-87)
  • setupRV (112-133)
  • navigateToFilter (165-168)
  • toggleLayoutMode (196-218)
  • navigateToDetail (135-162)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (3)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (1)
  • observeViewModel (111-126)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (4)
  • observeViewModel (85-106)
  • observeFilterResult (62-82)
  • setupRV (108-129)
  • navigateToFilter (160-164)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (4)
  • observeViewModel (89-110)
  • observeFilterResult (67-87)
  • setupRV (112-133)
  • navigateToFilter (164-168)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (1)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (2)
  • openNaverMap (218-243)
  • updateBookmarkUI (246-251)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (5)
  • setupMap (81-90)
  • initTagView (209-216)
  • initViewPager (140-157)
  • copyToClipboard (188-193)
  • updateBookmarkUI (246-251)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (5)
  • setupMap (80-89)
  • initTagView (200-207)
  • initViewPager (135-152)
  • copyToClipboard (180-185)
  • updateBookmarkUI (236-241)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (7)
  • initRVAdapter (171-194)
  • observeViewModel (89-110)
  • observeFilterResult (67-87)
  • setupRV (112-133)
  • navigateToFilter (165-168)
  • toggleLayoutMode (196-218)
  • navigateToDetail (135-162)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (7)
  • initRVAdapter (170-196)
  • observeViewModel (89-110)
  • observeFilterResult (67-87)
  • setupRV (112-133)
  • navigateToFilter (164-168)
  • toggleLayoutMode (199-221)
  • navigateToDetail (135-162)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (1)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (9)
  • observeViewModel (111-126)
  • fetchDetailData (101-109)
  • initListener (173-197)
  • initTagView (229-236)
  • initViewPager (154-171)
  • setupMap (83-98)
  • openNaverMap (238-263)
  • copyToClipboard (199-204)
  • updateBookmarkUI (266-271)
🪛 detekt (1.23.8)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt

[warning] 44-44: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)


[warning] 45-45: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)

🔇 Additional comments (25)
app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchFilterFragment.kt (1)

126-165: 날짜 및 필터 로직이 잘 구현되어 있어요.

날짜 범위 처리 로직(startDate/endDate 조합)과 빈 필터 체크가 모두 올바르게 구현되어 있고, SavedStateHandle을 통한 결과 전달 패턴도 적절해요.

app/src/main/java/com/example/findu/presentation/ui/search/filter/SearchBreedSelector.kt (1)

25-158: 전반적인 구조가 탄탄해요.

품종 선택 UI 로직(칩 관리, 드롭다운 토글, 최대 개수 제한, TextWatcher 무한루프 방지 등)이 잘 구현되어 있고, suppressWatcher 플래그로 프로그래밍 방식의 텍스트 변경 시 이벤트 루프를 방지하는 패턴도 적절해요.

app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (2)

59-65: ✅ 이전 리뷰의 critical 이슈가 해결되었습니다!

onViewCreated에서 observeViewModel()getSearchData()를 호출하도록 수정되어 viewLifecycleOwner 접근 시점 문제가 해결되었습니다. 이제 Fragment의 view lifecycle이 준비된 후에 안전하게 실행됩니다.


112-123: SearchAnimal.location은 non-nullable String이므로 null 체크 불필요
SearchAllFragmentitem.location ?: "" 연산자는 제거하고, 두 곳 모두 item.location으로 일관 사용하세요.

app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (3)

56-61: ✅ lifecycle 이슈 해결 확인!

이전 리뷰에서 지적된 viewLifecycleOwner 접근 시점 문제가 onViewCreated로 호출 위치를 옮겨서 올바르게 해결되었습니다.


108-129: 데이터 매핑 로직이 올바르게 구현되었습니다.

null-safe 연산자를 사용하여 thumbnailImageUrllocation 필드를 안전하게 처리하고, reportId를 사용한 새로운 데이터 모델로 정확하게 매핑하고 있습니다.


222-233: SearchListListener 구현이 깔끔합니다.

각 이벤트 핸들러가 명확하게 구현되어 있고, 단일 표현식 함수로 간결하게 작성되어 가독성이 좋습니다.

app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (3)

59-65: ✅ 이전 커밋에서 해결된 이슈 확인!

Past review comment에 명시된 대로 viewLifecycleOwner 접근 시점 문제가 commit 5a120dd에서 이미 해결되었습니다. onViewCreated에서 올바르게 호출하고 있습니다.


89-110: ViewModel 관찰 로직이 잘 구현되었습니다.

데이터와 에러를 별도의 Flow로 수집하여 적절하게 UI를 업데이트하고 있습니다. collectLatest를 사용하여 최신 데이터만 처리하는 것도 좋은 선택입니다.


112-133: location 필드 처리 확인

Line 118의 location = item.location이 SearchRescueFragment와 동일하게 null-safe 연산자 없이 처리되고 있는 반면, SearchAllFragment는 location ?: ""를 사용합니다.

REPORTING과 PROTECTING 타입에서 location이 항상 non-null임이 보장된다면 문제없지만, 그렇지 않다면 SearchRescueFragment와 함께 일관성 있게 처리하는 것이 좋겠습니다.

SearchRescueFragment의 리뷰 코멘트를 참고해주세요.

app/src/main/java/com/example/findu/data/mapper/todomain/SearchResponseDtoMapper.kt (3)

21-28: 신규 DTO 필드 매핑(LGTM)

reportId/date/location/interest 매핑이 v2 스키마와 일치합니다.


40-47: SearchStatus → SearchRvTag 매핑(LGTM)

상태-뷰 태그 변환 깔끔합니다.


14-16: lastId null 처리 정책 재확인 필요
Mapper에서 null-1로 대체하는 방식이 초기 호출(default: Long.MAX_VALUE) 및 페이징 종료 로직과, 백엔드 API 계약과 일치하는지 확인하세요.

app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (2)

83-97: 지도 준비 전 위치 보관 로직(LGTM)

pendingLocation으로 초기 위치 유실을 막는 접근 좋습니다.


154-169: 이미지 ViewPager 설정(LGTM)

마진/오프스크린 설정 포함해 무난합니다.

app/src/main/java/com/example/findu/data/mapper/todomain/DetailSearchResponseDtoMapper.kt (4)

12-33: DetailProtect 매핑(LGTM)

위도/경도, 발견일자 등 v2 필드 매핑이 정확합니다.


35-51: DetailMissing 매핑(LGTM)

누락 없음으로 보입니다.


53-66: DetailWitness 매핑(LGTM)

스키마에 맞게 반영되어 있습니다.


77-84: Status → RV Tag 매핑(LGTM)

UNKNOWN 포함해 누락 없습니다.

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

15-22: 보호 상세 조회 체인(LGTM)

handleBaseResponse → getOrThrow → toDomain 패턴 깔끔합니다.

app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (2)

117-133: UI 바인딩(LGTM)

필드 매핑, 태그/이미지/지도 초기화 흐름 모두 정상입니다.


164-176: 주소 복사 기능 추가(LGTM)

빈 값 가드와 토스트 처리까지 좋습니다.

app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (2)

119-137: UI 바인딩(LGTM)

필드/태그/이미지/지도 초기화 무난합니다.


176-184: 주소 복사 기능(LGTM)

가드/토스트 처리 적절합니다.

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

43-77: 상세 조회 시 isInterested 동기화(LGTM)

각 상세 조회 성공 시 관심 상태를 갱신하는 흐름이 추가되어 UI 반영이 좋아졌습니다.

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.

재사용을 위해 분리를 많이 하신 점 좋은 것 같습니다!
다만 분리된 게 많아 파악하기가 쉽지 않아서 주석들 추가해주시면 좋을 것 같습니다!

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

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/presentation/ui/my/MyViewModel.kt (1)

38-38: 타입 불일치로 인한 기능 오류를 수정해야 합니다.

deleteInterestReportAnimalUseCase 파라미터의 타입이 PostInterestReportAnimalUseCase로 되어 있어, 관심 해제 시 관심 등록이 실행되는 문제가 발생합니다.

다음과 같이 수정하세요:

     private val postInterestProtectingAnimalUseCase: PostInterestProtectingAnimalUseCase,
     private val postInterestReportAnimalUseCase: PostInterestReportAnimalUseCase,
     private val deleteInterestProtectingAnimalUseCase: DeleteInterestProtectingAnimalUseCase,
-    private val deleteInterestReportAnimalUseCase: PostInterestReportAnimalUseCase,
+    private val deleteInterestReportAnimalUseCase: DeleteInterestReportAnimalUseCase,
     private val deleteReportUseCase: DeleteReportUseCase,
app/src/main/java/com/example/findu/data/mapper/todomain/SearchResponseDtoMapper.kt (1)

33-39: 태그 파싱 예외 처리 보완 필요

서버에서 새로운 태그 문자열이 내려오면 IllegalArgumentException이 터지면서 앱이 바로 크래시 납니다. 외부 입력은 항상 예상치 못한 값이 섞일 수 있으니, 알 수 없는 값은 SearchStatus.UNKNOWN 등으로 안전하게 흡수해 주세요.

-        else -> throw IllegalArgumentException("Unknown tag value: $this")
+        else -> SearchStatus.UNKNOWN
♻️ Duplicate comments (1)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (1)

137-138: 중성화 정보 바인딩 오류 수정 필요

Line [137]에서 중성화 텍스트 뷰에 공고번호를 넣고 있어 잘못된 정보가 노출됩니다. DetailProtectData가 내려주는 중성화 여부 필드를 사용하도록 수정해 주세요 (예: data.neutering).

🧹 Nitpick comments (9)
app/build.gradle.kts (1)

80-82: Firebase Config 라이브러리 alias 정리 필요

libs.google.firebase.config.ktxlibs.firebase.config.ktx가 동일 모듈을 가리키도록 중복 선언되어 있어요. Gradle Catalog에서 하나만 유지하고 여기서는 그 alias만 쓰도록 정리하면 가독성과 관리성이 좋아집니다.

gradle/libs.versions.toml (1)

51-55: 중복 alias 제거 제안

firebase-config-ktxgoogle-firebase-config-ktx가 동일 모듈(com.google.firebase:firebase-config-ktx)을 중복 정의하고 있습니다. 하나만 남기고 사용하는 쪽을 맞추면 혼선을 줄일 수 있어요.

다음처럼 정리하면 됩니다:

- firebase-config-ktx = { module = "com.google.firebase:firebase-config-ktx" }
- google-firebase-config-ktx = { module = "com.google.firebase:firebase-config-ktx" }
+ firebase-config-ktx = { module = "com.google.firebase:firebase-config-ktx" }

그리고 build.gradle.kts에서는 implementation(libs.firebase.config.ktx)로 맞춰주세요.

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

188-203: 현재 사용되지 않는 코드예요.

isCurrentVersionLatest 함수는 이전 리뷰에서 제안된 대로 semantic versioning을 올바르게 구현하고 있어요 (좋습니다! 👍). 하지만 setupVersion()이 주석 처리되어 있어서 지금은 호출되지 않는 dead code 상태예요.

옵션:

  1. 버전 체크 복구가 곧 이루어질 예정이면 그대로 두기
  2. 복구 시점이 불확실하면 일단 제거했다가 나중에 다시 추가하기

개인적으로는 곧 복구될 예정이면 그냥 두는 게 편할 것 같아요!

app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (1)

84-99: 코드 중복을 줄이는 리팩토링을 고려해 보세요.

setupMap, copyToClipboard, openNaverMap, initTagView 등의 메서드가 SearchDisappearDetailFragment(그리고 아마도 SearchProtectDetailFragment)와 동일하게 구현되어 있어요. 다음과 같은 방법으로 중복을 제거할 수 있습니다:

  1. BaseSearchDetailFragment 추상 클래스를 만들어서 공통 로직 상속
  2. 유틸리티 클래스나 확장 함수로 추출

코드 유지보수성을 위해 나중에 고려해 보시면 좋을 것 같아요.

Also applies to: 190-195, 210-217, 219-244

app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (1)

115-128: 뷰 라이프사이클 스코프로 전환 권장

Fragment.lifecycleScopeonDestroyView 이후에도 살아 있어서, 다음 렌더링 전에 플로우가 다시 방출되면 해제된 뷰 바인딩을 만질 위험이 있습니다. viewLifecycleOwner.lifecycleScope(또는 repeatOnLifecycle)로 옮겨서 뷰가 사라지면 곧바로 수집이 중단되도록 바꿔 주세요.

-        lifecycleScope.launch {
+        viewLifecycleOwner.lifecycleScope.launch {
             viewModel.detailMissingData.collectLatest { data ->
                 data?.let { updateUI(it) }
             }
         }

-        lifecycleScope.launch {
+        viewLifecycleOwner.lifecycleScope.launch {
             viewModel.errorMessage.collectLatest { message ->
                 message?.let {
                     Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show()
                 }
             }
         }
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (4)

47-47: 사용되지 않는 변수 제거 필요

items 변수가 선언되어 있지만 코드 어디에서도 사용되지 않네요. 실제 데이터는 ViewModel의 flow에서 받아오고 있어서 이 변수는 불필요합니다.

다음 diff를 적용해서 정리해주세요:

-    private var items = ArrayList<SearchAnimal>()
-
     override fun onCreateView(

78-81: delay(50) 매직 넘버

필터 적용 후 데이터 재조회 전에 delay(50) 처리가 있는데, 다른 Fragment들(SearchAllFragment, SearchReportFragment)과 동일한 패턴이긴 하지만 명확한 설명 없는 매직 넘버네요.

특정 타이밍 이슈를 회피하기 위한 것이라면, 상수로 추출하고 주석으로 이유를 남겨두는 게 좋을 것 같아요.

+    companion object {
+        // 필터 상태 업데이트 후 데이터 재조회 전 대기 시간 (UI 상태 동기화 보장)
+        private const val FILTER_UPDATE_DELAY_MS = 50L
+    }
+
     private fun observeFilterResult() {
         ...
         viewLifecycleOwner.lifecycleScope.launch {
-            delay(50)
+            delay(FILTER_UPDATE_DELAY_MS)
             viewModel.getSearchData(SearchType.PROTECTING, lastProtectId)
         }

115-118: null 안전성 일관성 체크

Line 115에서는 thumbnailImageUrl ?: ""로 null 처리를 해주는데, line 118의 location은 null 체크가 없네요.

SearchAllFragment에서는 item.location ?: ""로 처리하고 있어서, 여기도 동일하게 null 안전성을 추가하는 게 좋을 것 같아요.

 SearchRv(
     image = item.thumbnailImageUrl ?: "",
     name = item.title,
     date = item.date,
-    location = item.location,
+    location = item.location ?: "",
     isBookmark = item.interest,
     tag = item.tag.toSearchRvTag(),
     reportId = item.reportId
 )

135-162: 파라미터 이름 명확성 개선

함수 파라미터가 cardId로 되어 있는데, 실제로는 line 237에서 item.reportId로 전달되고 있네요. API v2 전환 후 reportId 기반으로 통일되었다면, 파라미터 이름도 reportId로 변경하는 게 더 명확할 것 같아요.

-private fun navigateToDetail(cardId: Long, tag: String, name: String) {
+private fun navigateToDetail(reportId: Long, tag: String, name: String) {
     when (tag) {
         "보호중" ->
             findNavController().navigate(
                 SearchFragmentDirections.actionFragmentSearchToFragmentSearchDetailProtecting(
-                    id = cardId.toString(),
+                    id = reportId.toString(),
                     tag = tag,
                     name = name
                 )
             )
 
         "목격신고" -> findNavController().navigate(
             SearchFragmentDirections.actionFragmentSearchToFragmentSearchDetailWitness(
-                id = cardId.toString(),
+                id = reportId.toString(),
                 tag = tag,
                 name = name
             )
         )
 
         "실종신고" -> findNavController().navigate(
             SearchFragmentDirections.actionFragmentSearchToFragmentSearchDetailDisappear(
-                id = cardId.toString(),
+                id = reportId.toString(),
                 tag = tag,
                 name = name
             )
         )
     }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35efaac and 4c748ed.

📒 Files selected for processing (15)
  • app/build.gradle.kts (2 hunks)
  • app/src/main/java/com/example/findu/data/mapper/todomain/SearchResponseDtoMapper.kt (2 hunks)
  • app/src/main/java/com/example/findu/data/repositoryimpl/SearchRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/mapper/todomain/MyInterestDataMapper.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/my/MyFragment.kt (2 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/my/MyViewModel.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (8 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (5 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (6 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (6 hunks)
  • app/src/main/res/layout/fragment_search_detail_protecting.xml (3 hunks)
  • build.gradle.kts (1 hunks)
  • gradle/libs.versions.toml (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/res/layout/fragment_search_detail_protecting.xml
🧰 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/SearchResponseDtoMapper.kt
🧬 Code graph analysis (7)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (1)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (6)
  • setupMap (86-101)
  • initTagView (220-227)
  • initViewPager (151-168)
  • openNaverMap (229-254)
  • copyToClipboard (199-204)
  • updateBookmarkUI (257-262)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (7)
  • initRVAdapter (172-196)
  • observeViewModel (89-110)
  • observeFilterResult (67-87)
  • setupRV (112-133)
  • navigateToFilter (165-169)
  • toggleLayoutMode (198-220)
  • navigateToDetail (135-162)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (7)
  • initRVAdapter (170-196)
  • observeViewModel (89-110)
  • observeFilterResult (67-87)
  • setupRV (112-133)
  • navigateToFilter (164-168)
  • toggleLayoutMode (199-221)
  • navigateToDetail (135-162)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (3)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (1)
  • observeViewModel (114-129)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (6)
  • observeViewModel (86-107)
  • observeFilterResult (63-83)
  • setupRV (109-130)
  • navigateToFilter (161-165)
  • toggleLayoutMode (194-216)
  • navigateToDetail (132-159)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (6)
  • observeViewModel (89-110)
  • observeFilterResult (67-87)
  • setupRV (112-133)
  • navigateToFilter (165-169)
  • toggleLayoutMode (198-220)
  • navigateToDetail (135-162)
app/src/main/java/com/example/findu/presentation/ui/my/MyFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/my/MyViewedAnimalFragment.kt (1)
  • observeViewModel (89-102)
app/src/main/java/com/example/findu/presentation/ui/my/MyReportHistoryFragment.kt (1)
  • observeViewModel (83-94)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (1)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (5)
  • setupMap (86-101)
  • initTagView (220-227)
  • initViewPager (151-168)
  • copyToClipboard (199-204)
  • updateBookmarkUI (257-262)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt (1)
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt (9)
  • setupMap (86-101)
  • observeViewModel (114-129)
  • fetchDetailData (104-112)
  • initListener (177-201)
  • initTagView (233-240)
  • initViewPager (158-175)
  • openNaverMap (242-267)
  • copyToClipboard (203-208)
  • updateBookmarkUI (270-275)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchReportFragment.kt (1)
app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchAllFragment.kt (5)
  • observeViewModel (86-107)
  • observeFilterResult (63-83)
  • setupRV (109-130)
  • navigateToFilter (161-165)
  • navigateToDetail (132-159)
🔇 Additional comments (15)
app/src/main/java/com/example/findu/presentation/ui/my/MyViewModel.kt (1)

16-16: 패키지 구조 개선이 잘 적용되었습니다.

todomain 패키지로 매퍼 import를 이동한 것은 프로젝트 전반의 아키텍처 개편과 일치하며, 도메인 중심의 데이터 변환 로직을 체계화하는 좋은 방향입니다.

app/src/main/java/com/example/findu/presentation/ui/my/MyFragment.kt (2)

68-72: 좋은 개선입니다!

onViewCreated에서 observeViewModel()을 호출하는 패턴은 Android 라이프사이클 모범 사례를 따르고 있어요. 이전에는 ViewModel의 상태를 관찰하는 코드가 호출되지 않았는데, 이제 제대로 작동할 거예요. 다른 Fragment들(MyViewedAnimalFragment, MyReportHistoryFragment)과도 일관된 패턴이네요.


177-186: 버전 체크 기능이 완전히 비활성화되어 있어요.

PR 설명에서 언급하신 것처럼 디바이스 ID 문제로 임시로 주석 처리하신 것 같은데, 머지 전에 다음 사항들을 확인해주세요:

  • 주석 처리된 코드가 복구되어야 해요
  • BuildConfig import가 필요해요 (현재 제거됨)
  • remoteConfig 객체가 초기화되고 임포트되어야 해요
  • 이전 리뷰 코멘트에서 ikseong00님이 언급하신 Remote Config 구현이 필요해요

머지 전에 이 기능들을 복구할 계획이 맞는지 확인 부탁드려요!

app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt (9)

4-6: 새로운 기능에 맞게 import가 잘 추가되었습니다.

클립보드 복사 기능과 새로운 도메인 모델/매퍼를 위한 import가 적절히 추가되었네요.

Also applies to: 20-21, 23-23, 25-25


38-38: 새로운 ViewModel로 잘 전환되었습니다.

DetailSearchViewModel을 통해 witness 데이터를 받아오는 구조로 깔끔하게 변경되었네요.


47-47: 지연된 위치 설정이 잘 구현되었습니다.

pendingLocation을 사용해서 지도가 준비되기 전에 위치 데이터가 도착하는 경우를 잘 처리하고 있어요. SearchDisappearDetailFragment와 동일한 패턴을 따르고 있네요.

Also applies to: 57-59, 84-99


101-109: 목격 신고 데이터를 가져오는 로직이 정확합니다.

새로운 API 호출이 올바르게 적용되었어요.


111-125: ViewModel 관찰 로직이 잘 업데이트되었습니다.

detailWitnessData Flow를 올바르게 수집하고 있어요.


127-143: UI 업데이트 로직이 새로운 데이터 모델에 맞게 잘 수정되었습니다.

DetailWitnessData의 모든 필드가 적절히 바인딩되고 있어요.


178-195: 주소 복사 기능이 잘 구현되었습니다.

클립보드 API를 올바르게 사용하고 있고, 빈 주소에 대한 처리도 잘되어 있어요.


210-217: 태그 뷰 초기화가 새로운 매퍼를 사용하도록 업데이트되었습니다.

toDetailSearchStatustoDetailSearchRvTag를 통해 태그 정보를 잘 변환하고 있어요.


145-162: SearchDetailVPAdapter가 String URL과 Int 리소스 ID를 모두 처리하므로 initViewPager 변경에 이상 없습니다.

app/src/main/java/com/example/findu/presentation/ui/search/tablayout/SearchRescueFragment.kt (3)

59-65: 이전 리뷰 이슈 해결 확인됨 ✅

observeViewModel()observeFilterResult() 호출을 onViewCreated로 이동한 것 확인했습니다. 이제 viewLifecycleOwner.lifecycleScope 접근 시 IllegalStateException이 발생하지 않을 거예요.


89-110: LGTM - ViewModel 관찰 로직 정상

protectSearchDataerrorMessage flow 관찰이 적절하게 구현되어 있고, viewLifecycleOwner.lifecycleScope도 올바르게 사용되고 있네요.


229-248: SearchListListener 구현 깔끔하게 완료 👍

리스너 인터페이스의 모든 메서드가 명확하게 구현되어 있고, 한글 주석도 잘 달려있어서 가독성이 좋네요. 각 메서드가 적절한 네비게이션이나 뷰모델 호출로 연결되어 있습니다.

Comment on lines 197 to 208
private fun initBookmarkUI() {
binding.ivSearchDetailBookmark.setOnClickListener {
isBookmarked = !isBookmarked
viewModel.setInterestReportAnimal(cardId)
updateBookmarkUI(isBookmarked)
viewModel.toggleInterestWitness(cardId)
}

lifecycleScope.launchWhenStarted {
viewModel.isInterested.collectLatest { interested ->
updateBookmarkUI(interested)
isBookmarked = interested
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Deprecated API 사용을 최신 방식으로 변경해 주세요.

launchWhenStarted는 deprecated되었어요. 최신 권장 방식인 repeatOnLifecycle을 사용하는 것이 좋습니다.

다음과 같이 수정할 수 있어요:

-        lifecycleScope.launchWhenStarted {
-            viewModel.isInterested.collectLatest { interested ->
-                updateBookmarkUI(interested)
-                isBookmarked = interested
-            }
+        lifecycleScope.launch {
+            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.isInterested.collectLatest { interested ->
+                    updateBookmarkUI(interested)
+                    isBookmarked = interested
+                }
+            }
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private fun initBookmarkUI() {
binding.ivSearchDetailBookmark.setOnClickListener {
isBookmarked = !isBookmarked
viewModel.setInterestReportAnimal(cardId)
updateBookmarkUI(isBookmarked)
viewModel.toggleInterestWitness(cardId)
}
lifecycleScope.launchWhenStarted {
viewModel.isInterested.collectLatest { interested ->
updateBookmarkUI(interested)
isBookmarked = interested
}
}
}
private fun initBookmarkUI() {
binding.ivSearchDetailBookmark.setOnClickListener {
viewModel.toggleInterestWitness(cardId)
}
lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.isInterested.collectLatest { interested ->
updateBookmarkUI(interested)
isBookmarked = interested
}
}
}
}
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt
around lines 197 to 208, replace the deprecated lifecycleScope.launchWhenStarted
usage with the recommended repeatOnLifecycle pattern: start a
lifecycleScope.launch block and inside call
repeatOnLifecycle(Lifecycle.State.STARTED) to collect viewModel.isInterested
with collectLatest, updating the UI and isBookmarked as before; ensure you
import androidx.lifecycle.Lifecycle and use the fragment's lifecycle to call
repeatOnLifecycle.

@nasohee nasohee merged commit a3994ca into develop Oct 13, 2025
1 check passed
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