Conversation
There was a problem hiding this comment.
♻️ Duplicate comments (1)
feature/stats/detail/src/main/java/com/twix/stats/detail/StatsDetailViewModel.kt (1)
64-66:⚠️ Potential issue | 🟠 Major
collectLatest취소가 실제 네트워크 요청 취소로 이어지지 않습니다.이전 리뷰에서도 지적된 내용이지만, 아직 해결되지 않아 다시 언급합니다.
왜 문제인가요?
fetchStatsDetail(yearMonth)(Line 115)은 일반 함수로, 내부에서launchResult→viewModelScope.launch로 독립적인 코루틴을 띄운 뒤 즉시 반환합니다.- 결과적으로
collectLatest블록이 새로운 월 선택으로 취소될 시점에는,fetchStatsDetail()은 이미 리턴된 상태이고 네트워크 요청 코루틴은viewModelScope에 살아 있습니다.- 느린 이전 월 요청이 빠른 새 월 요청보다 늦게 완료되면 UI 상태가 오래된 데이터로 덮어씌워지는 응답 역전(response ordering) 문제가 발생합니다.
어떻게 개선할 수 있나요?
fetchStatsDetail을suspend함수로 변경하여collectLatest스코프 내에서 완료를 기다리도록 하는 방법이 가장 깔끔합니다.🔧 개선 방안: fetchStatsDetail을 suspend 함수로 리팩터링
- private fun fetchStatsDetail(date: YearMonth) { - if (checkCache(date)) return - launchResult( - block = { statsRepository.fetchStatsDetail(currentState.goalId, date) }, - onSuccess = { handleFetchStatsDetailSuccess(it) }, - onError = { - reduceDetailWithEmptyCompletedDate(date) - showToast(R.string.toast_fetch_stats_failed, ToastType.ERROR) - }, - ) - } + private suspend fun fetchStatsDetail(date: YearMonth) { + if (checkCache(date)) return + when (val result = statsRepository.fetchStatsDetail(currentState.goalId, date)) { + is AppResult.Success -> handleFetchStatsDetailSuccess(result.data) + is AppResult.Error -> { + handleError(result.error) + reduceDetailWithEmptyCompletedDate(date) + showToast(R.string.toast_fetch_stats_failed, ToastType.ERROR) + } + } + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/stats/detail/src/main/java/com/twix/stats/detail/StatsDetailViewModel.kt` around lines 64 - 66, The collectLatest cancellation isn't cancelling the actual network request because fetchStatsDetail(yearMonth) starts a new independent coroutine via launchResult/viewModelScope.launch and returns immediately; change fetchStatsDetail to a suspend function that performs the network call in the caller coroutine (remove the internal viewModelScope.launch/launchResult usage), have it use structured concurrency (e.g., suspend functions + withContext or coroutineScope) to perform the request and return the result or update state before returning, and then call the new suspend fetchStatsDetail(yearMonth) directly inside collectLatest so cancellation of collectLatest cancels the in-flight request and prevents stale UI updates.
🧹 Nitpick comments (1)
feature/stats/detail/src/main/java/com/twix/stats/detail/StatsDetailViewModel.kt (1)
191-199: 변수명과 실제 타입 간의 의미 불일치를 개선하면 어떨까요?
previousMonth와nextMonth는 이름만 보면YearMonth타입처럼 느껴지지만, 실제로는LocalDate(해당 월의 특정 일 포함)입니다. 이후YearMonth.from(previousMonth)로 다시 변환하는 것도 이 때문인데, 처음부터YearMonth타입으로 계산하면 의도가 더 명확해집니다.✨ 타입 명확화 제안
private fun fetchPreviousMonth() { - val previousMonth = currentState.detail.yearMonth.minusMonths(1) - reduce { copy(detail = detail.copy(yearMonth = previousMonth)) } - monthChangeFlow.tryEmit(YearMonth.from(previousMonth)) + val previousYearMonth = YearMonth.from(currentState.detail.yearMonth).minusMonths(1) + reduce { copy(detail = detail.copy(yearMonth = previousYearMonth.atDay(1))) } + monthChangeFlow.tryEmit(previousYearMonth) } private fun fetchNextMonth() { - val nextMonth = currentState.detail.yearMonth.plusMonths(1) - reduce { copy(detail = detail.copy(yearMonth = nextMonth)) } - monthChangeFlow.tryEmit(YearMonth.from(nextMonth)) + val nextYearMonth = YearMonth.from(currentState.detail.yearMonth).plusMonths(1) + reduce { copy(detail = detail.copy(yearMonth = nextYearMonth.atDay(1))) } + monthChangeFlow.tryEmit(nextYearMonth) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/stats/detail/src/main/java/com/twix/stats/detail/StatsDetailViewModel.kt` around lines 191 - 199, previousMonth and nextMonth are being treated as LocalDate then converted back to YearMonth; instead compute and keep them as YearMonth directly in fetchPreviousMonth and fetchNextMonth by using currentState.detail.yearMonth.minusMonths(1) / .plusMonths(1), change the variable types to YearMonth, and emit monthChangeFlow.tryEmit(previousMonth) / tryEmit(nextMonth) (remove the YearMonth.from(...) conversions) while keeping the existing reduce { copy(detail = detail.copy(yearMonth = ...)) } updates.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In
`@feature/stats/detail/src/main/java/com/twix/stats/detail/StatsDetailViewModel.kt`:
- Around line 64-66: The collectLatest cancellation isn't cancelling the actual
network request because fetchStatsDetail(yearMonth) starts a new independent
coroutine via launchResult/viewModelScope.launch and returns immediately; change
fetchStatsDetail to a suspend function that performs the network call in the
caller coroutine (remove the internal viewModelScope.launch/launchResult usage),
have it use structured concurrency (e.g., suspend functions + withContext or
coroutineScope) to perform the request and return the result or update state
before returning, and then call the new suspend fetchStatsDetail(yearMonth)
directly inside collectLatest so cancellation of collectLatest cancels the
in-flight request and prevents stale UI updates.
---
Nitpick comments:
In
`@feature/stats/detail/src/main/java/com/twix/stats/detail/StatsDetailViewModel.kt`:
- Around line 191-199: previousMonth and nextMonth are being treated as
LocalDate then converted back to YearMonth; instead compute and keep them as
YearMonth directly in fetchPreviousMonth and fetchNextMonth by using
currentState.detail.yearMonth.minusMonths(1) / .plusMonths(1), change the
variable types to YearMonth, and emit monthChangeFlow.tryEmit(previousMonth) /
tryEmit(nextMonth) (remove the YearMonth.from(...) conversions) while keeping
the existing reduce { copy(detail = detail.copy(yearMonth = ...)) } updates.
ℹ️ Review info
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
feature/stats/detail/src/main/java/com/twix/stats/detail/StatsDetailViewModel.kt
- `GoalManageViewModel`에서 목표 완료 또는 삭제 성공 시 `StatsRefreshBus`를 통해 통계 화면 갱신을 알리는 로직 추가
dogmania
left a comment
There was a problem hiding this comment.
고생하셨습니다! 이제 각자 구현한 게 많아졌다 보니까 코드 읽기에 도움되는 방향으로 리뷰 남겨봤어요!
| enum class Target { | ||
| InProgress, | ||
| End, | ||
| All, | ||
| } |
There was a problem hiding this comment.
이제 각자 구현한 코드도 많아졌고, 기존에 존재하던 파일을 수정했을 때 좀 더 수월하게 리뷰할 수 있도록 주석을 추가하면 좋을 것 같아요! Target이 어디에 쓰이는 건지 간단한 설명이 있으면 코드 읽을 때 도움될 것 같슴다
There was a problem hiding this comment.
좋은 생각인 것 같아 ! 주석을 남기면 나중에 내가 봤을 때도 다시 코드의 의도를 떠올리기 좋은 것 같아
우선 여기만 반영하고 데모 끝나면 전체적으로 리팩터링 하면서 주석 추가하도록 할게 👍
리뷰 반영 커밋 : 4da8df8
| val status: String, | ||
| val monthDate: LocalDate, | ||
| val isCompleted: Boolean, | ||
| val yearMonth: LocalDate, |
There was a problem hiding this comment.
이 변수 타입이랑 사용되는 용도를 봤을 때 yearMonth보다는 currentDate가 더 직관적일 거 같아요! StatsCalendarUiModel에서도 currentDate라는 변수명을 사용하고 있으니까 통일성에도 좋을 것 같습니다
이슈 번호
#110
작업내용
StatsRepository/StatsDetail모델을 서버 응답 구조에 맞게 정리StatsDetailViewModel초기 로딩을 요약/캘린더 데이터 분리 처리로 개선결과물
리뷰어에게 추가로 요구하는 사항 (선택)