21 KiB
크리에이터 랭킹 페이지 Plan / Task
For agentic workers: 구현 시
superpowers:subagent-driven-development또는superpowers:executing-plans를 사용해 task 단위로 진행한다. 각 단계는 체크박스(- [ ])로 추적하고, 완료 즉시- [x]로 갱신한다.
Goal: HomeMainFragment의 Text Tab bar에서 랭킹 선택 시 GET /api/v2/home/rankings/creators 응답을 기존 creatorranking 위젯으로 표시한다.
Architecture: 신규 홈 크리에이터 랭킹 API, Repository, ViewModel, DTO/UI model/mapper는 기존 홈 추천 패턴과 같은 kr.co.vividnext.sodalive.v2.main.home 하위에 둔다. 화면은 HomeMainFragment와 fragment_v2_main_home.xml을 최소 확장하고, 기존 kr.co.vividnext.sodalive.v2.widget.creatorranking 위젯은 showRankChange=false 숨김 옵션만 필요한 만큼 확장한다.
Tech Stack: Kotlin, Android XML Views, ViewBinding, RecyclerView, RxJava3, Retrofit, Gson, Koin, Coil, Robolectric/local unit test.
전제와 성공 기준
- PRD:
docs/20260608_크리에이터_랭킹_페이지/prd.md - Figma:
24:5654 - Capsule Tab bar(
주간 인기,지금 뜨는 중,남성 인기,여성 인기)는 배치하지 않는다. rankChange는null/0->Stay, 양수 ->Increase, 음수 ->Decrease로 매핑한다.isNew=true는rankChange보다 우선해New로 매핑한다.showRankChange=false이면 모든 rank-num 영역을 완전히 숨긴다.- API 응답은 서버에서
rank오름차순으로 내려오지만, 클라이언트에서도 한 번 더rank기준 오름차순 정렬한다. creatorId=0은 차단 관계로 보고isBlocked=true, 클릭 불가로 처리한다.creatorId>0item만UserProfileActivity로 이동하며, 별도 analytics/logging은 추가하지 않는다.- 구현 완료 후 최소 다음 명령을 실행한다.
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.creatorranking.*"./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*"./gradlew :app:mergeDebugResources./gradlew :app:compileDebugKotlin./gradlew :app:ktlintCheck
파일 구조
- 생성:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeCreatorRankingModels.ktHomeCreatorRankingResponse,HomeCreatorRankingItemResponseDTO를 정의한다.
- 생성:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeCreatorRankingApi.ktGET /api/v2/home/rankings/creatorsRetrofit endpoint를 정의한다.
- 생성:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeCreatorRankingRepository.kt- API 호출을 감싸고 기존 repository 패턴과 동일하게 token을 전달한다.
- 생성:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeCreatorRankingMappers.kt- API 응답을
CreatorRankingItem목록으로 변환한다.
- API 응답을
- 생성:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeCreatorRankingUiState.ktLoading,Content,Empty,Error상태를 정의한다.
- 생성:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeCreatorRankingViewModel.kt- 랭킹 API 호출, loading, toast, state를 관리한다.
- 수정:
app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt- 신규 API, Repository, ViewModel을 Koin에 등록한다.
- 수정:
app/src/main/res/layout/fragment_v2_main_home.xmlTextTabBarView아래에 랭킹 전용RecyclerView를 추가한다.
- 수정:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt랭킹탭 전환, 랭킹 adapter, ViewModel observe, 프로필 이동을 연결한다.
- 수정:
app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingItem.ktshowRankChange표시 계약을 추가한다.
- 수정:
app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingLargeCardView.kt - 수정:
app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingCompactCardView.kt - 수정:
app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingHorizontalCardView.ktshowRankChange=false일 때 rank-num 영역을GONE처리한다.
- 테스트 수정/생성:
- 수정:
app/src/test/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingItemTest.kt - 수정:
app/src/test/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingAdapterLayoutTest.kt - 생성:
app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeCreatorRankingMapperTest.kt - 수정:
app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt
- 수정:
Phase 1: 기존 구조 확인과 작업 경계 고정
-
Task 1.1: 기존 홈/랭킹 위젯/DI 구조 확인
- 확인:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt - 확인:
app/src/main/res/layout/fragment_v2_main_home.xml - 확인:
app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt - 확인:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationApi.kt - 확인:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeRecommendationViewModel.kt - 확인:
app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingAdapter.kt - 확인:
app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingItem.kt - 검증: 기존 추천 content는
nsv_home_recommendation_content아래에 있고, 랭킹 content는 별도 container가 필요함을 확인한다.
- 확인:
-
Task 1.2: 구현 제외 범위 재확인
- 확인:
docs/20260608_크리에이터_랭킹_페이지/prd.md - 제외:
- Capsule Tab bar
- 팔로잉 탭 content
- analytics/logging
- ViewPager2/swipe 전환
- 검증: 계획 문서의 모든 phase가 위 제외 범위를 침범하지 않는지 확인한다.
- 확인:
Phase 2: creatorranking 위젯 rank-num 숨김 계약 확장
-
Task 2.1:
CreatorRankingItem표시 계약 테스트 추가- 수정:
app/src/test/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingItemTest.kt - 추가 테스트:
- 기본
showRankChange는true showRankChange=false인 item은 rank change 표시 대상이 아님creatorId=0,isBlocked=trueitem은isTouchable=false
- 기본
- 검증 명령:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.creatorranking.CreatorRankingItemTest" - 기대 결과:
showRankChange속성 미구현으로 RED 실패.
- 수정:
-
Task 2.2:
CreatorRankingItem에showRankChange추가- 수정:
app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingItem.kt - 구현 내용:
val showRankChange: Boolean = true를 기본값 있는 마지막 파라미터로 추가한다.- 기존 테스트/호출부가 깨지지 않도록 기본값을 유지한다.
- 검증 명령:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.creatorranking.CreatorRankingItemTest" - 기대 결과: PASS.
- 수정:
-
Task 2.3: rank-num 숨김 view 테스트 추가
- 수정:
app/src/test/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingAdapterLayoutTest.kt - 추가 테스트:
Large,Compact,Horizontalcard에showRankChange=falseitem을 bind하면ll_creator_ranking_delta가GONEshowRankChange=trueitem을 bind하면 기존처럼ll_creator_ranking_delta가VISIBLE
- 검증 명령:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.creatorranking.CreatorRankingAdapterLayoutTest" - 기대 결과: view bind 미구현으로 RED 실패.
- 수정:
-
Task 2.4: rank-num 숨김 구현
- 수정:
app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingLargeCardView.kt - 수정:
app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingCompactCardView.kt - 수정:
app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingHorizontalCardView.kt - 구현 내용:
bindDelta(item)시작부에서item.showRankChange == false이면 delta container를View.GONE으로 설정하고 return한다.item.showRankChange == true이면 delta container를View.VISIBLE로 복구한 뒤 기존 presentation 적용을 유지한다.
- 검증 명령:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.creatorranking.*" - 기대 결과: PASS.
- 수정:
Phase 3: 홈 크리에이터 랭킹 API/DTO/mapper 작성
-
Task 3.1: mapper RED 테스트 작성
- 생성:
app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeCreatorRankingMapperTest.kt - 테스트 케이스:
- 응답 item을
rank오름차순으로 재정렬한다. isNew=true는RankingChangeType.New, amount0으로 매핑한다.rankChange=null또는0은RankingChangeType.Stay, amount0으로 매핑한다.rankChange=5는RankingChangeType.Increase, amount5로 매핑한다.rankChange=-3은RankingChangeType.Decrease, amount3으로 매핑한다.showRankChange=false이면 모든CreatorRankingItem.showRankChange=false로 매핑한다.creatorId=0은isBlocked=true,isTouchable=false로 매핑한다.rank < 1item은 제외한다.
- 응답 item을
- 검증 명령:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeCreatorRankingMapperTest" - 기대 결과: DTO/mapper 미구현으로 RED 실패.
- 생성:
-
Task 3.2: API DTO와 mapper 구현
- 생성:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeCreatorRankingModels.kt - 생성:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeCreatorRankingMappers.kt - 구현 내용:
data class HomeCreatorRankingResponse(val showRankChange: Boolean, val items: List<HomeCreatorRankingItemResponse>)data class HomeCreatorRankingItemResponse(val rank: Int, val rankChange: Int?, val isNew: Boolean, val creatorId: Long, val nickname: String, val profileImageUrl: String)fun HomeCreatorRankingResponse.toCreatorRankingItems(): List<CreatorRankingItem>rank >= 1filtering,ranksorting,RankingChangeTypemapping,abs(rankChange)amount mappingcreatorId == 0L->isBlocked=true
- 검증 명령:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeCreatorRankingMapperTest" - 기대 결과: PASS.
- 생성:
-
Task 3.3: API/Repository 작성
- 생성:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeCreatorRankingApi.kt - 생성:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeCreatorRankingRepository.kt - 구현 내용:
@GET("/api/v2/home/rankings/creators")fun getCreatorRankings(@Header("Authorization") authHeader: String): Single<ApiResponse<HomeCreatorRankingResponse>>- repository는
getCreatorRankings(token: String)으로 API를 위임한다.
- 검증 명령:
./gradlew :app:compileDebugKotlin - 기대 결과: 신규 API/Repository 컴파일 성공.
- 생성:
Phase 4: ViewModel과 DI 등록
-
Task 4.1: UI state와 ViewModel 작성
- 생성:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeCreatorRankingUiState.kt - 생성:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeCreatorRankingViewModel.kt - 구현 내용:
HomeCreatorRankingUiState.LoadingHomeCreatorRankingUiState.Content(val items: List<CreatorRankingItem>)HomeCreatorRankingUiState.EmptyHomeCreatorRankingUiState.Error(val message: String?)rankingStateLiveData,isLoading,toastLiveDataloadCreatorRankings()- 기존
HomeRecommendationViewModel과 동일하게SharedPreferenceManager.token, RxJava scheduler, unknown error toast 패턴 사용
- 검증 명령:
./gradlew :app:compileDebugKotlin - 기대 결과: ViewModel 컴파일 성공.
- 생성:
-
Task 4.2: Koin DI 등록
- 수정:
app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt - 구현 내용:
- import 추가:
HomeCreatorRankingApi,HomeCreatorRankingRepository,HomeCreatorRankingViewModel networkModule에single { ApiBuilder().build(get(), HomeCreatorRankingApi::class.java) }repositoryModule에factory { HomeCreatorRankingRepository(get()) }viewModelModule에viewModel { HomeCreatorRankingViewModel(get()) }
- import 추가:
- 검증 명령:
./gradlew :app:compileDebugKotlin - 기대 결과: Koin 등록과 import 컴파일 성공.
- 수정:
Phase 5: 홈 레이아웃과 탭 전환 UI 연결
-
Task 5.1: 홈 레이아웃에 랭킹 목록 추가 RED 테스트 작성
- 수정:
app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt - 추가 테스트:
fragment_v2_main_home.xml에rv_home_creator_rankings가 존재한다.rv_home_creator_rankings는TextTabBarView아래에 직접 constraint 된다.- Capsule Tab bar 관련 view id가 존재하지 않는다.
- 초기 visibility는
GONE이다.
- 검증 명령:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home ranking layout*" - 기대 결과: 신규 RecyclerView 미존재로 RED 실패.
- 수정:
-
Task 5.2:
fragment_v2_main_home.xml에 랭킹 RecyclerView 추가- 수정:
app/src/main/res/layout/fragment_v2_main_home.xml - 구현 내용:
androidx.recyclerview.widget.RecyclerView추가- id:
@+id/rv_home_creator_rankings - width/height:
0dp - top:
@id/text_tab_bar_homebottom - bottom/start/end: parent
android:visibility="gone"android:clipToPadding="false"android:paddingHorizontal="@dimen/spacing_14"android:paddingTop="@dimen/spacing_14"android:paddingBottom="@dimen/spacing_28"
- 제외: Capsule Tab bar view 추가 금지.
- 검증 명령:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home ranking layout*"및./gradlew :app:mergeDebugResources - 기대 결과: PASS.
- 수정:
-
Task 5.3:
HomeMainFragment탭 전환/adapter 연결 테스트 작성- 수정:
app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt - 추가 테스트:
CreatorRankingAdapter.createGridLayoutManager()가 랭킹 RecyclerView에 사용되는 소스 계약랭킹index 선택 시 추천 content는GONE, 랭킹 RecyclerView는VISIBLE추천index 선택 시 추천 content는VISIBLE, 랭킹 RecyclerView는GONEcreatorId=0item은 프로필 이동 intent를 만들지 않는다.creatorId>0item은UserProfileActivity+Constants.EXTRA_USER_IDintent를 만든다.
- 검증 명령:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home ranking*" - 기대 결과: Fragment 연결 미구현으로 RED 실패.
- 수정:
-
Task 5.4:
HomeMainFragment에 랭킹 탭 content 연결- 수정:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt - 구현 내용:
private val homeCreatorRankingViewModel: HomeCreatorRankingViewModel by viewModel()private val creatorRankingAdapter = CreatorRankingAdapter { openCreatorRankingProfile(it) }rvHomeCreatorRankings.layoutManager = CreatorRankingAdapter.createGridLayoutManager(requireContext())rvHomeCreatorRankings.adapter = creatorRankingAdapter- tab index 상수:
HOME_TAB_RECOMMENDATION = 0,HOME_TAB_RANKING = 1,HOME_TAB_FOLLOWING = 2 setOnTabSelectedListener에서 추천/랭킹만 content 전환 처리한다.- 랭킹 최초 선택 시
homeCreatorRankingViewModel.loadCreatorRankings()를 호출한다. HomeCreatorRankingUiState.Content이면creatorRankingAdapter.submitItems(items)Empty/Error이면 빈 목록을 submit한다.openCreatorRankingProfile(item)은item.creatorId > 0일 때만UserProfileActivity를Constants.EXTRA_USER_ID와 함께 실행한다.- 별도 analytics/logging은 추가하지 않는다.
- 검증 명령:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home ranking*"및./gradlew :app:compileDebugKotlin - 기대 결과: PASS.
- 수정:
Phase 6: 통합 검증과 문서 기록
-
Task 6.1: targeted test 실행
- 실행:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.creatorranking.*"./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeCreatorRankingMapperTest"./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"
- 기대 결과: 모두
BUILD SUCCESSFUL. - 실패 시: 실패 원인을 이 문서의 Verification Log에 누적하고, 수정 전 관련 task 체크박스를 되돌린다.
- 실행:
-
Task 6.2: compile/resource/lint 검증
- 실행:
./gradlew :app:mergeDebugResources./gradlew :app:compileDebugKotlin./gradlew :app:ktlintCheck
- 기대 결과: 모두
BUILD SUCCESSFUL. - 참고: 기존
.editorconfig disabled_rulesdeprecation warning은 신규 실패가 아니면 별도 수정하지 않는다.
- 실행:
-
Task 6.3: 최종 문서 검증 기록 누적
- 수정:
docs/20260608_크리에이터_랭킹_페이지/plan-task.md - 수정:
docs/20260608_크리에이터_랭킹_페이지/prd.md - 구현 내용:
- 실행한 명령, 성공/실패 결과, 실패 시 원인과 보완 내용을 Verification Log에 누적한다.
- 기존 Verification Log는 삭제하거나 덮어쓰지 않는다.
- 검증: PRD와 plan-task의 완료 상태와 실제 구현 상태가 일치한다.
- 수정:
Verification Log
- 2026-06-08:
superpowers:writing-plans지침, PRDdocs/20260608_크리에이터_랭킹_페이지/prd.md, 기존 홈 추천 계획 문서,fragment_v2_main_home.xml,HomeMainFragment,AppDI, 기존 홈 추천 API/ViewModel,creatorranking위젯 구조와 테스트 위치를 확인했다. - 2026-06-08: 이번 단계는 계획 문서 작성만 수행했으며 구현/빌드/테스트는 실행하지 않았다.
- 2026-06-08: Phase 1 범위로 PRD와 기존
HomeMainFragment,fragment_v2_main_home.xml,AppDI, 홈 추천 API/Repository/ViewModel,creatorranking위젯/테스트 구조를 재확인했다. Capsule Tab bar, 팔로잉 탭 content, analytics/logging, ViewPager2/swipe 전환은 Phase 1-3 구현 범위에서 제외했다. - 2026-06-08: Phase 2 TDD RED로
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.creatorranking.CreatorRankingItemTest"를 실행해showRankChange미구현 컴파일 실패를 확인했다. 이후CreatorRankingItem.showRankChange기본값을 추가하고 동일 명령이BUILD SUCCESSFUL로 통과했다. - 2026-06-08: Phase 2 rank-num 숨김 TDD RED로
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.creatorranking.CreatorRankingAdapterLayoutTest"를 실행했다. 최초 Robolectric 설정 누락으로 Koin 초기화 오류가 발생해 기존 위젯 테스트 패턴과 동일하게@Config(sdk = [28], application = Application::class)를 적용했고, 이후 Large/Compact/Horizontal 숨김 assertion 3건 실패를 확인했다. 세 카드 view에showRankChange=false시ll_creator_ranking_delta를GONE처리하도록 구현한 뒤./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.creatorranking.*"가BUILD SUCCESSFUL로 통과했다. - 2026-06-08: Phase 3 mapper TDD RED로
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeCreatorRankingMapperTest"를 실행해HomeCreatorRankingResponse,HomeCreatorRankingItemResponse,toCreatorRankingItems미구현 컴파일 실패를 확인했다. 이후 DTO와 mapper를 추가하고 동일 명령이BUILD SUCCESSFUL로 통과했다. - 2026-06-08: Phase 3 API/Repository를 추가한 뒤
./gradlew :app:compileDebugKotlin이BUILD SUCCESSFUL로 통과했다. 검증 중 병렬 Gradle 실행 1건에서classes.jarzip header 오류가 발생했으나, 동일 타겟을 단독 재실행해./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.creatorranking.*",./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeCreatorRankingMapperTest",./gradlew :app:compileDebugKotlin,./gradlew :app:ktlintCheck모두BUILD SUCCESSFUL을 확인했다..editorconfig disabled_rulesdeprecation warning은 기존 경고로 보고 수정하지 않았다. - 2026-06-08: Phase 4로
HomeCreatorRankingUiState,HomeCreatorRankingViewModel을 추가하고AppDI에HomeCreatorRankingApi,HomeCreatorRankingRepository,HomeCreatorRankingViewModel을 등록했다. 검증 명령./gradlew :app:compileDebugKotlin이BUILD SUCCESSFUL로 통과했다. Gradle deprecated feature warning은 기존 빌드 경고로 보고 수정하지 않았다.