Files
sodalive-android/docs/20260625_메인_홈_팔로잉_탭/reviews/phase9-news-nested-payload-review.md

10 KiB

Phase 9 최근 소식 nested payload 리뷰

1. 리뷰 정보

항목 내용
리뷰 대상 Phase 9 / Task 9.1~9.7
기준 commit 또는 working tree f600d10e8b63b6fdc812b18a874b077dfe072ca8 + 2026-07-31 working tree
리뷰 일자 2026-07-31
리뷰어 Codex
기준 문서 대상 prd.md, plan-task.md
리뷰 상태 수정 검증 완료

2. 리뷰 목적과 범위

목적

  • type별 nested payload DTO, mapper 필터, nullable image, adapter binding과 target ID 보존을 확인한다.
  • Task 9.7의 미사용 FeedAdapter·FeedImageViews 제거가 실제 호출자 0건과 일치하는지 확인한다.

포함 범위

  • HomeFollowingModels.kt, HomeFollowingUiModels.kt, HomeFollowingMappers.kt, HomeFollowingNewsAdapter.kt
  • 삭제된 v2/widget/feed/FeedAdapter.kt와 현재 재사용 중인 FeedItem, FeedRankView, FeedContentView, FeedCommunityView
  • mapper/source tests와 content ranking string resource

제외 범위

  • 실제 서버 응답 샘플과 Phase 11/13 터치 목적지.

3. 판정 기준

  • PRD의 nested payload 필드, matching payload null 제외, contentImageUrl: String?, visibleFromAtUtc 정책을 기준으로 판정했다.

4. 검토한 근거

  • HomeFollowingModels.kt:47-111
  • HomeFollowingMappers.kt:64-149
  • HomeFollowingUiModels.kt:55-103
  • HomeFollowingNewsAdapter.kt:68-167
  • HomeFollowingMapperTest.kt:88-182

실행한 검증

명령 결과 핵심 증거
mapper + Fragment source focused test 성공 mapper 10, source 16 테스트 실패 0
팔로잉 전체 회귀 성공 39개 테스트 실패 0
resource/compile/ktlint/diff 성공 모두 PASS
nullable image binding·Coil 2.7.0 source·저장소 dispose() 사용례 대조 문제 확인 blank 분기가 drawable만 비우고 View 요청을 해제하지 않음
DTO→mapper→Feed item의 ID 의미 대조 문제 확인 content/post ID가 creator ID 필드에 대입됨
`rg -n "FeedAdapter FeedImageViews" app/src/main/java app/src/test/java docs/20260625_메인_홈_팔로잉_탭 docs/agent-guides` 성공

5. 발견 사항 요약

ID 심각도 상태 제목 소유 Task 후속 goal
REV-P9-001 Medium 수정 완료 nullable 최근 소식 이미지 바인딩이 이전 Coil 요청을 해제하지 않음 Task 9.5 P9-R1 완료
REV-P9-002 Low 수정 완료 API에 없는 creator ID를 content/post ID로 합성해 Feed model에 전달함 Task 9.6 P9-R2 완료

6. 발견 사항 상세

matching payload가 null이면 mapNotNull로 제외되고, content image null은 UI model까지 보존된다. visibleFromAtUtc 기준 상대 시간과 payload별 target ID도 현재 계약과 일치한다. Task 9.7에서 삭제한 FeedAdapterFeedImageViews는 production/test 호출자가 없고, 최근 소식은 현재 FeedItem과 타입별 Feed view를 직접 재사용하므로 삭제에 따른 확정 발견 사항은 없다.

REV-P9-001 — nullable 최근 소식 이미지 바인딩이 이전 Coil 요청을 해제하지 않음

  • 심각도: Medium
  • 상태: 수정 완료
  • 관련 요구사항: PRD contentImageUrl·imageUrl nullable 계약, Task 9.3 Feed image fallback
  • 소유 Task: Task 9.5

관찰 내용

HomeFollowingNewsAdapter.bindImage()는 blank URL에서 setImageDrawable(null)만 호출한다. 같은 ViewHolder가 이미지가 있는 item에서 nullable image item으로 재사용될 때 이전 Coil 요청이 진행 중이면, 그 요청이 완료되면서 현재 item에 이전 이미지가 표시될 수 있다.

근거

  • 코드: HomeFollowingNewsAdapter.kt:187-203의 blank 분기에 dispose()가 없음
  • 계약: FollowingContentNewsResponse.contentImageUrl, FollowingContentRankingNewsResponse.contentImageUrl, FollowingCommunityPostNewsResponse.imageUrl은 nullable
  • 테스트: HomeFollowingMapperTest는 null 보존만 확인하고 adapter 재사용 시 이미지 요청 상태는 검증하지 않음
  • 저장소 정상 사용례: HomePopularCommunityAdapter.kt:67-70은 blank image에서 imageView.dispose()와 drawable 초기화를 함께 수행함
  • Coil 2.7.0 로컬 source: ImageView.dispose()는 View의 현재 요청을 취소하고 분리함

재현 또는 검증 절차

  1. 최근 소식 ViewHolder의 image view에 item A의 완료 전 Coil 요청을 연결한다.
  2. 같은 holder에 contentImageUrl = null 또는 imageUrl = null인 item B를 바인딩한다.
  3. 현재 blank 분기는 drawable만 비우므로 요청 A가 남는다.
  4. 요청 A가 완료되면 B에 A의 이미지가 표시될 수 있다. 요구 결과는 blank 바인딩 시 요청을 해제하고 빈 이미지 상태를 유지하는 것이다.

영향

빠른 스크롤이나 느린 이미지 응답 조건에서 ranking/content/community 최근 소식 카드에 다른 item의 이미지가 노출될 수 있다.

권장 조치

공유되는 bindImage() blank 분기에서 imageView.dispose() 후 drawable을 비운다. 한 대표 nullable content item의 ViewHolder 재사용을 Robolectric test로 고정하고 mapper/Feed 회귀를 실행한다.

판정 기록

  • 2026-07-30 — nullable DTO·adapter binding·Coil request lifecycle·저장소 내 정상 해제 패턴을 대조해 확정.
  • 2026-07-30 — 구현 전 Phase 9 Task 9.5 / P9-R1로 전환.
  • 2026-07-30 — Task 9.5에서 HomeFollowingNewsAdapter.bindImage() blank 분기에 dispose()를 추가하고, Robolectric adapter binding test와 팔로잉 회귀 검증을 완료.

REV-P9-002 — API에 없는 creator ID를 다른 target ID로 합성함

  • 심각도: Low
  • 상태: 수정 완료
  • 관련 요구사항: Task 6.4의 API 미제공 값 비합성 정책, Phase 9 nested payload 계약
  • 소유 Task: 신규 Task 9.6

관찰 내용

FollowingContentNewsResponseFollowingCommunityPostNewsResponse에는 creatorId가 없지만 adapter가 FeedItem.Content.creatorId에는 content.targetId, FeedItem.Community.creatorId에는 community.targetId를 문자열로 넣는다. 두 target은 각각 contentId, postId이므로 creator ID가 아니다.

근거

  • DTO: HomeFollowingModels.kt의 content/community nested payload에는 creatorId 없음
  • mapper: content targetId = contentId, community targetId = postId
  • adapter: HomeFollowingNewsAdapter.kt에서 두 FeedItem.creatorId에 각각 위 target ID를 대입
  • 현재 클릭 경로: adapter callback은 원본 HomeFollowingNewsUiItem을 캡처하므로 현행 상세 이동에는 잘못된 creatorId가 사용되지 않음

재현 또는 검증 절차

  1. content/community nested payload와 UI model의 target ID 의미를 대조한다.
  2. 두 Feed item binding의 creatorId 대입값을 확인한다.
  3. 실제 결과는 content/post ID가 creator ID 필드에 들어가며, 요구 결과는 서버가 제공하지 않은 ID를 합성하지 않는 것이다.

영향

현재 화면 클릭에는 직접 영향이 없지만 Feed model의 의미가 잘못되고, 공통 Feed 위젯이 추후 creator ID를 사용하면 잘못된 크리에이터 이동이나 분석 값으로 이어질 수 있다.

권장 조치

서버 계약을 추정해 확장하지 말고 두 Feed item의 creatorId를 빈 값으로 전달한다. 실제 contentId, postId와 기존 원본 UI model click callback은 유지한다.

판정 기록

  • 2026-07-31 — DTO→mapper→adapter→click callback을 추적해 확정하고 Phase 9 Task 9.6 / P9-R2로 전환.
  • 2026-07-31 — Task 9.6에서 FeedItem.ContentFeedItem.CommunitycreatorId를 빈 값으로 전달하도록 수정하고 focused/팔로잉 회귀 검증을 완료.

7. 확정 항목의 plan·goal 전환

  • Phase 9에 Task 9.5 / P9-R1을 추가했으며 구현·검증을 완료했다.
  • 최종 재점검에서 Task 9.6 / P9-R2를 추가했으며 creator ID 합성 제거를 완료했다.
  • Task 9.7 / P9-R3의 미사용 FeedAdapter 제거와 문서 정리를 현재 호출자 검색·컴파일·회귀 결과로 확인했다.

8. 리뷰 종료 판정

판정 항목 결과 근거
리뷰 범위 전체 확인 충족 DTO→mapper→adapter 추적과 Task 9.7 미사용 호출자 검색
후보 항목 판정 완료 충족 REV-P9-001~002 판정 완료
확정 항목 plan 반영 충족 Task 9.5, Task 9.6 추가
검증 명령과 결과 기록 충족 focused/통합 PASS

최종 결론: 확정 발견 사항 모두 수정 완료

남은 항목: 없음

9. 수정 후 검증 기록

  • 2026-07-30: REV-P9-001 보완으로 HomeFollowingNewsAdapter.bindImage()의 blank image 분기에서 기존 Coil 요청을 dispose()로 해제한 뒤 drawable을 비우도록 변경했다. 구현 전 HomeFollowingNewsAdapterTest RED, 구현 후 focused GREEN, 팔로잉 전체 회귀와 신규 프로필 테스트, :app:mergeDebugResources, :app:compileDebugKotlin, :app:ktlintCheck, git diff --check PASS.
  • 2026-07-31 재점검: nested payload mapper·nullable image 회귀를 포함한 Phase 1~13 focused 113개 test, resource merge, Kotlin compile, ktlint PASS. 추가 확정 발견 사항 없음.
  • 2026-07-31 최종 재점검: API에 없는 creator ID를 content/post ID로 대신 채우는 binding을 REV-P9-002로 확정하고, production/test 코드를 변경하기 전에 Task 9.6 / P9-R2로 전환했다.
  • 2026-07-31 최종 검증: nested payload·adapter 회귀를 포함한 통합 20개 suite·306개 test와 resource merge, Kotlin compile, ktlint가 PASS했다. 자동 검증 성공과 별개로 ID 의미 계약 불일치 REV-P9-002는 Task 9.6의 수정 항목으로 유지했다.
  • 2026-07-31: REV-P9-002 보완으로 HomeFollowingNewsAdapter의 content/community Feed model에서 API가 제공하지 않는 creatorId를 content/post ID로 합성하지 않고 빈 값으로 전달하도록 변경했다. RED focused test 실패를 확인한 뒤 GREEN으로 전환했고, focused test, 팔로잉 전체 회귀, :app:compileDebugKotlin, :app:ktlintCheck PASS.
  • 2026-07-31 이번 리뷰 재검증: nested payload·nullable image·ID 의미와 Task 9.7의 미사용 FeedAdapter 삭제 상태를 다시 대조했다. production/test FeedAdapter|FeedImageViews 호출자는 0건이며, 통합 16개 suite·120개 test, resource merge, Kotlin compile, ktlint가 PASS해 추가 확정 발견 사항이 없다.