Skip to content

통계 목록 조회 API 연동#103

Merged
chanho0908 merged 7 commits intodevelopfrom
feat/#88-stats-detail-api
Feb 25, 2026
Merged

통계 목록 조회 API 연동#103
chanho0908 merged 7 commits intodevelopfrom
feat/#88-stats-detail-api

Conversation

@chanho0908
Copy link
Member

@chanho0908 chanho0908 commented Feb 25, 2026

이슈 번호

#88

작업내용

  • 통계 API 연동을 위해 네트워크 계층에 StatsServiceStatsResponse/StatsMapper를 추가
  • 도메인 StatsRepositoryYearMonth + StatsStatus 기반 시그니처로 변경
  • 데이터 계층을 API 기반 DefaultStatsRepository로 교체하고, 기존 FakeStatsRepository는 제거
  • StatsViewModel을 신규 인터페이스에 맞춰 정리하고, 월 이동 시 YearMonth 단위 캐싱/디바운스로 불필요한 호출을 줄였습니다.
  • StatsDetailViewModelYearMonth 기반 상세 조회 하도록 수정

결과물

  • 기능 변경(통계 API 연동/조회 방식 변경)으로 별도 UI 스크린샷은 없습니다.

리뷰어에게 추가로 요구하는 사항 (선택)

@chanho0908 chanho0908 changed the title ✨ Feat: Stats API 연동 및 YearMonth 기반 통계 조회 구조 개편 통계 목록 조회 API 연동 Feb 25, 2026
@chanho0908 chanho0908 changed the base branch from develop to feat/#88-stats-detail-feature February 25, 2026 11:02
@chanho0908 chanho0908 self-assigned this Feb 25, 2026
@chanho0908 chanho0908 added the Feature Extra attention is needed label Feb 25, 2026
Base automatically changed from feat/#88-stats-detail-feature to develop February 25, 2026 15:45
@chanho0908
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
feature/main/src/main/java/com/twix/stats/StatsViewModel.kt (1)

27-27: 월 캐시에 상한을 두면 메모리 안정성이 더 좋아집니다.

현재 캐시는 월 이동 횟수만큼 계속 커질 수 있습니다. LRU 형태로 최대 보관 개수를 제한하는 방향을 고려해볼까요?

♻️ 제안 수정안
-    private val inProgressStatsCache = mutableMapOf<YearMonth, Stats>()
+    private val inProgressStatsCache =
+        object : LinkedHashMap<YearMonth, Stats>(MAX_CACHE_SIZE, 0.75f, true) {
+            override fun removeEldestEntry(
+                eldest: MutableMap.MutableEntry<YearMonth, Stats>?,
+            ): Boolean = size > MAX_CACHE_SIZE
+        }

     companion object {
         private const val DEBOUNCE_INTERVAL = 300L
+        private const val MAX_CACHE_SIZE = 24
     }

Also applies to: 133-135

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/main/src/main/java/com/twix/stats/StatsViewModel.kt` at line 27, The
inProgressStatsCache (mutableMapOf<YearMonth, Stats>) can grow unbounded;
replace it with a bounded LRU map to limit entries and improve memory stability
(e.g., use a LinkedHashMap/evicting map or a small in-memory cache library) and
apply the same change to the other cache instances referenced around the
StatsViewModel (the maps used for per-month stats at lines near 133-135). Ensure
the replacement preserves key type YearMonth and value type Stats, enforces a
configurable max size, and evicts least-recently-used entries on access.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@core/network/src/main/java/com/twix/network/model/response/stats/mapper/StatsMapper.kt`:
- Line 16: selectedDate parsing in StatsMapper uses
LocalDate.parse(selectedDate) which fails for yyyy-MM responses from the
YearMonth-based StatsService; update the mapping in StatsMapper to try multiple
parsing strategies (e.g., attempt LocalDate.parse with yyyy-MM-dd, fall back to
parsing YearMonth with DateTimeFormatter.ofPattern("yyyy-MM") and convert to a
LocalDate (e.g., first day of month), and handle DateTimeParseException by
logging or providing a safe default) so the selectedDate assignment is resilient
to both "yyyy-MM-dd" and "yyyy-MM" inputs.

In `@data/src/main/java/com/twix/data/repository/DefaultStatsRepository.kt`:
- Around line 28-33: The TODO in DefaultStatsRepository.fetchStatsDetail
currently throws NotImplementedError and causes crashes; implement the method to
call the data source (StatsService) to fetch details and convert the response
via the existing mapper into a domain StatsDetail, returning
AppResult.success(...) on success and AppResult.failure(...) (or the module's
error wrapper) on any exception or non-2xx response; ensure you use the same
function signature (suspend fun fetchStatsDetail(goalId: Long, date: YearMonth):
AppResult<StatsDetail>), handle HTTP/IO errors with try/catch, map service DTOs
to domain via the repository's mapper, and avoid throwing — return a safe
AppResult instead.

In `@domain/src/main/java/com/twix/domain/model/enums/StatsStatus.kt`:
- Line 13: The current StatsStatus.fromApi uses runCatching { valueOf(value)
}.getOrElse { IN_PROGRESS } which masks unknown/typo/back-end-added enum values
by mapping them to IN_PROGRESS; change this to either return a new safe enum
constant (e.g., add UNKNOWN to StatsStatus and map failures to UNKNOWN) or
propagate the failure to the caller (throw an IllegalArgumentException) so
callers can handle unexpected values; update fromApi to use valueOf(value) with
explicit handling (return UNKNOWN or rethrow) and ensure any code paths using
fromApi handle the new UNKNOWN or exception accordingly.

In `@feature/main/src/main/java/com/twix/stats/StatsViewModel.kt`:
- Around line 63-68: The async response can overwrite the UI when navigating
months quickly: in the launchResult success handler for launchResult/block
calling statsRepository.fetchStats (which currently writes to
inProgressStatsCache[date] and calls reduce { copy(inProgressStats = it) }), add
a guard that only updates the cache and calls reduce if the request's date/month
matches the current selected month in the ViewModel's state (compare the date
parameter to the ViewModel's selected-month state field, e.g.,
selectedDate/selectedMonth); keep using inProgressStatsCache and
reduce/inProgressStats but skip updates when they differ.

In
`@feature/stats/detail/src/main/java/com/twix/stats/detail/StatsDetailViewModel.kt`:
- Around line 47-56: Restore the initial stats fetch and ensure month-change
triggers call fetchStatsDetail: uncomment or re-enable the initial
fetchStatsDetail(...) call in the init block (using argDate -> LocalDate.parse
-> YearMonth fallback to YearMonth.now()), and inside the collectMonthChangeFlow
subscription (where monthChangeFlow is collected) call
fetchStatsDetail(yearMonth) whenever a new YearMonth is emitted; keep
reduceNavArguments() and collectMonthChangeFlow() but ensure
fetchStatsDetail(...) is invoked both initially and on each monthChangeFlow
emission.

---

Nitpick comments:
In `@feature/main/src/main/java/com/twix/stats/StatsViewModel.kt`:
- Line 27: The inProgressStatsCache (mutableMapOf<YearMonth, Stats>) can grow
unbounded; replace it with a bounded LRU map to limit entries and improve memory
stability (e.g., use a LinkedHashMap/evicting map or a small in-memory cache
library) and apply the same change to the other cache instances referenced
around the StatsViewModel (the maps used for per-month stats at lines near
133-135). Ensure the replacement preserves key type YearMonth and value type
Stats, enforces a configurable max size, and evicts least-recently-used entries
on access.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d21ed05 and 13bf44e.

📒 Files selected for processing (14)
  • core/network/src/main/java/com/twix/network/di/ApiServiceModule.kt
  • core/network/src/main/java/com/twix/network/model/response/stats/mapper/StatsMapper.kt
  • core/network/src/main/java/com/twix/network/model/response/stats/model/StatsResponse.kt
  • core/network/src/main/java/com/twix/network/service/StatsService.kt
  • data/src/main/java/com/twix/data/di/RepositoryModule.kt
  • data/src/main/java/com/twix/data/repository/DefaultStatsRepository.kt
  • data/src/main/java/com/twix/data/repository/FakeStatsRepository.kt
  • domain/src/main/java/com/twix/domain/model/enums/StatsStatus.kt
  • domain/src/main/java/com/twix/domain/repository/StatsRepository.kt
  • feature/main/src/main/java/com/twix/stats/StatsScreen.kt
  • feature/main/src/main/java/com/twix/stats/StatsViewModel.kt
  • feature/main/src/main/java/com/twix/stats/contract/StatsUiState.kt
  • feature/main/src/main/java/com/twix/stats/preview/StatsUiStatePreviewProvider.kt
  • feature/stats/detail/src/main/java/com/twix/stats/detail/StatsDetailViewModel.kt
💤 Files with no reviewable changes (1)
  • data/src/main/java/com/twix/data/repository/FakeStatsRepository.kt

@YAPP-Github YAPP-Github deleted a comment from coderabbitai bot Feb 25, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

📝 Walkthrough

Walkthrough

이 변경사항은 통계 데이터 조회 기능을 실제 네트워크 API 호출로 전환하는 종합적인 구조 개선입니다. 네트워크 서비스 계층에서 새로운 StatsService 인터페이스와 API 응답 모델들을 추가하고, 데이터 계층에서 가짜 데이터를 반환하던 FakeStatsRepository를 실제 API 호출을 수행하는 DefaultStatsRepository로 교체합니다. 도메인 계층에서는 날짜 타입을 LocalDate에서 YearMonth로 변경하고 통합된 fetchStats 메서드를 도입하여 API 계약을 단순화합니다. UI 계층에서는 월 기반 캐싱, 요청 중복 제거, 상태 매개변수 처리 등 새로운 로직이 추가되며, 상태 객체의 endStats 속성을 completedStats로 이름 변경합니다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

주요 검토 포인트 및 제안

1. 날짜 타입 전환에 따른 일관성 검증

현황: 도메인 계층에서 LocalDate에서 YearMonth로 변경되었으나, 몇 가지 불일치가 보입니다.

확인 필요 사항:

  • StatsDetailViewModel의 에러 처리에서 date.atDay(1)을 사용 중인데, 이것이 의도된 기본값인지 검토 필요합니다. 현재 로직이 실패 시 해당 월의 첫 날을 사용하려는 것이라면 명확한 코멘트가 도움될 것 같습니다.
  • API 응답의 selectedDateString으로 정의되어 있는데, 이를 LocalDate로 변환하는 과정에서 형식이 명확히 정의되어 있는지 확인하세요. (예: "2025-02" vs "2025-02-01")

2. StatsViewModel의 캐싱 및 요청 중복 제거 로직

강점: requestId 기반 추적과 latestInProgressRequestId를 통한 요청 신선도 검증 패턴이 우수합니다. 월 변경 시 debouncedistinctUntilChanged를 적용하여 불필요한 네트워크 호출을 줄인 점도 좋습니다.

개선 제안:

  • 캐시 무효화 전략이 명시되어 있지 않습니다. 어떤 상황에서 inProgressStatsCache를 초기화하나요? 사용자가 통계를 갱신하려 할 때 캐시를 비울 메커니즘이 필요해 보입니다.
  • 이벤트 버스 핸들러에서 InProgressCOMPLETED 이벤트를 다르게 처리하고 있는데, 각 경로가 이전의 fetchInProgressStats/fetchEndStats를 대체하는지 명확히 해주세요.

3. StatsDetailViewModel의 주석 처리된 코드

주의: fetchStatsDetail 호출이 주석 처리되어 있습니다.

질문:

  • 초기화 시점(initArgs)에서 바로 데이터를 가져오지 않는 의도가 무엇인가요?
  • collectMonthChangeFlow 내의 fetch 호출도 주석 처리되어 있는데, 사용자가 월을 변경했을 때 데이터를 언제 갱신하나요?
  • 이 변경사항이 의도된 것이라면, 별도의 이슈나 향후 작업으로 추적 중인지 확인 부탁합니다.

4. StatsMapper의 안정성

강점: runCatching을 사용하여 enum 변환 실패 시 기본값으로 폴백하는 방식이 우수합니다.

검토 사항:

  • StampType.CLOVERStampColor.GREEN400이 선택된 근거는 무엇인가요? 비즈니스 로직상 적절한 기본값인지 확인 필요합니다.
  • API 응답의 필드들이 모두 @SerialName으로 명시되어 있는 것은 좋지만, 백엔드 API 문서와의 동기화 절차가 있는지 확인하세요.

5. StatsRepository 인터페이스 변경의 영향도

주의: 이 변경은 공개 인터페이스의 시그니처 변경입니다.

확인 필요:

  • fetchInProgressStatsfetchEndStats를 제거했는데, 다른 모듈이나 화면에서 이들 메서드를 호출하고 있지는 않나요?
  • fetchStatsDetail의 매개변수가 LocalDate?에서 YearMonth로 변경되었는데, null 처리 로직의 변경 이유를 문서화하는 것이 좋겠습니다.

6. 속성 이름 변경 (endStats → completedStats)

긍정 평가: 이름이 더 명확하고 의도를 잘 드러냅니다. 변경 범위도 잘 관리되었습니다.

최종 확인: 모든 관련 UI 컴포넌트와 테스트가 이 변경을 반영했는지 재확인 부탁합니다.


종합 의견

이 PR은 가짜 데이터에서 실제 네트워크 API로의 전환이라는 중요한 마이그레이션을 담고 있습니다. 레이어 간 책임 분리가 명확하고, 캐싱과 요청 중복 제거 로직도 잘 설계되었습니다. 다만 StatsDetailViewModel의 주석 처리된 코드와 날짜 타입 변환 과정에서의 형식 명시화가 필요해 보입니다. 위의 질문들을 해결하신 후, 간단한 통합 테스트를 통해 월 변경 시 데이터 갱신 흐름을 검증해주시면 좋을 것 같습니다.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 전체 변경 사항의 핵심을 명확하게 요약하고 있습니다. '통계 목록 조회 API 연동'은 네트워크 계층 추가, 리포지토리 변경, 뷰모델 업데이트 등 모든 변경의 근본적인 목적을 정확히 전달합니다.
Description check ✅ Passed PR 설명이 이슈 번호, 상세한 작업 내용, 주요 변경 사항(네트워크 계층 추가, 리포지토리 변경, 뷰모델 리팩터링)을 체계적으로 제시하고 있어 변경 사항과 충분히 관련이 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#88-stats-detail-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
domain/src/main/java/com/twix/domain/model/enums/StatsStatus.kt (1)

12-14: ⚠️ Potential issue | 🟠 Major

알 수 없는 API 상태값이 IN_PROGRESS로 무음 폴백됩니다.

이 문제는 이전 리뷰에서 이미 언급되었으며, 코드에 아직 반영되지 않아 재언급합니다. 알 수 없는 value가 들어왔을 때 IN_PROGRESS로 강제 매핑하면, 백엔드에서 새로운 상태를 추가하거나 오타가 발생했을 때 "진행중" 상태로 오분류되어 도메인 의미가 왜곡됩니다.

개선 방향: UNKNOWN 값을 추가하거나, 최소한 로그를 남겨 운영 중 이슈를 추적할 수 있도록 하는 것을 권장합니다.

🛠️ 개선 예시
 enum class StatsStatus {
     NOT_STARTED,
     IN_PROGRESS,
     COMPLETED,
     DELETED,
+    UNKNOWN,
     ;

     fun toApi(): String = name

     companion object {
-        fun fromApi(value: String): StatsStatus = runCatching { valueOf(value) }.getOrElse { IN_PROGRESS }
+        fun fromApi(value: String): StatsStatus =
+            runCatching { valueOf(value) }.getOrElse {
+                Log.w("StatsStatus", "Unknown StatsStatus: $value, falling back to UNKNOWN")
+                UNKNOWN
+            }
     }
 }

As per coding guidelines [Domain 모듈 리뷰 가이드] "비즈니스 로직이 명확히 표현되어 있는가?" 규칙에 따라, 알 수 없는 상태를 특정 정상 상태(IN_PROGRESS)로 강제 매핑하는 것은 도메인 의미를 흐립니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@domain/src/main/java/com/twix/domain/model/enums/StatsStatus.kt` around lines
12 - 14, The fromApi function in companion object of StatsStatus currently
swallows unknown API values by mapping them to IN_PROGRESS; change it to either
return a dedicated UNKNOWN enum value (add UNKNOWN to StatsStatus) or throw/log
the unexpected value instead of defaulting to IN_PROGRESS so domain meaning
isn't corrupted; specifically update the companion object method fromApi to
detect missing value (valueOf failure) and map to StatsStatus.UNKNOWN or call
process/logger with the raw value and return a safe sentinel, ensuring
references to fromApi, StatsStatus, IN_PROGRESS are adjusted accordingly.
feature/stats/detail/src/main/java/com/twix/stats/detail/StatsDetailViewModel.kt (1)

47-56: ⚠️ Potential issue | 🔴 Critical

초기/월 변경 상세 조회가 비활성화되어 화면 데이터가 갱신되지 않습니다.

왜 문제인가: 초기 진입(Line 48-53)과 월 변경 수집(Line 63-65)에서 fetchStatsDetail(...) 호출이 주석 처리되어, 상태의 월만 바뀌고 실제 조회가 발생하지 않습니다.
어떻게 개선하나: init에서 초기 1회 조회를 복구하고, monthChangeFlow 수집부에서 fetchStatsDetail(yearMonth)를 다시 호출해 주세요. argDate가 있으면 LocalDate.parse(argDate)YearMonth.from(...)로 시작 월을 계산하는 방식이 안전합니다.

As per coding guidelines, [Feature 모듈 - MVI 패턴 리뷰 가이드]의 단방향 데이터 플로우(Intent → ViewModel → State → View)가 유지되는가? 규칙을 근거로 한 코멘트입니다.

Also applies to: 63-65

🤖 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 47 - 56, Restore the disabled initial and month-change detail
fetches: un-comment and call fetchStatsDetail(...) once in the init block (using
argDate?.let { LocalDate.parse(it) }?.let(YearMonth::from) ?: YearMonth.now() to
compute the start YearMonth) and also invoke fetchStatsDetail(yearMonth) inside
the monthChangeFlow collector in collectMonthChangeFlow()/where monthChangeFlow
is collected so that reduceNavArguments(), collectMonthChangeFlow(), and state
updates trigger an actual data load on init and on month changes.
data/src/main/java/com/twix/data/repository/DefaultStatsRepository.kt (1)

28-33: ⚠️ Potential issue | 🔴 Critical

fetchStatsDetail의 TODO는 런타임 크래시를 유발합니다.

왜 문제인가: 현재 구현은 호출 시 NotImplementedError로 종료되어 상세 화면 조회 플로우가 즉시 깨집니다.
어떻게 개선하나: TODO를 제거하고 실제 서비스 호출 + 매핑을 safeApiCall로 감싸 AppResult를 안전하게 반환해 주세요. 혹시 상세 API 시그니처가 이미 정해졌다면, 그 시그니처 기준으로 바로 연결해도 될까요?

As per coding guidelines, [Data 모듈 리뷰 가이드]의 Repository 구현 - Domain 인터페이스를 정확히 구현하는가? 규칙을 근거로 한 코멘트입니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@data/src/main/java/com/twix/data/repository/DefaultStatsRepository.kt` around
lines 28 - 33, Replace the TODO in DefaultStatsRepository.fetchStatsDetail with
a real implementation that wraps the remote call and mapping in safeApiCall and
returns an AppResult<StatsDetail>; specifically, call the repository's stats
API/client (the same client used elsewhere in this class), invoke the detail
endpoint for the given goalId and date, map the response to the domain
StatsDetail (using the existing mapper function, e.g., toStatsDetail() or
equivalent), and return the result from safeApiCall so errors are captured as
AppResult instead of throwing NotImplementedError.
🧹 Nitpick comments (4)
feature/main/src/main/java/com/twix/stats/preview/StatsUiStatePreviewProvider.kt (1)

56-56: completedStats 리네임이 두 프리뷰 상태 모두에 반영되었습니다! 👍

한 가지 선택적 제안: defaultState()에서도 completedStatsemptyList()로만 두면 종료된 통계 탭의 UI 렌더링을 Compose Preview에서 직접 확인하기 어렵습니다. EndStatsContent의 UI를 프리뷰로 검증하고 싶다면 defaultState에 샘플 데이터를 추가하는 것을 고려해보세요.

Also applies to: 66-66

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/main/src/main/java/com/twix/stats/preview/StatsUiStatePreviewProvider.kt`
at line 56, Update the preview data so Compose Preview shows the closed-stats
UI: in StatsUiStatePreviewProvider, populate completedStats with representative
sample items instead of emptyList() in the preview states (e.g., inside
defaultState() and the other preview state at the same file). Create a small
list of sample completed stat objects that match the
StatsUiState/EndStatsContent model and assign it to completedStats so
EndStatsContent renders meaningfully in the preview.
core/network/src/main/java/com/twix/network/model/response/stats/mapper/StatsMapper.kt (1)

38-40: valueOf 파싱 실패 시 로그 없이 기본값으로 폴백되어 API 불일치를 추적하기 어렵습니다.

왜 문제가 되나요? StampType.valueOf(this)StampColor.valueOf(this)는 대소문자를 구분합니다. API가 "clover""green400" 같이 다른 케이싱으로 응답하면 runCatching이 예외를 삼키고 조용히 기본값을 반환합니다. 이렇게 되면 실제 API 불일치가 앱에서 전혀 드러나지 않아 디버깅이 어려워집니다.

어떻게 개선할 수 있을까요? 최소한 폴백 발생 시 로그를 남겨 추적 가능하도록 하는 것을 권장합니다.

🛠️ 개선 예시
+import android.util.Log

-private fun String.toStampType(): StampType = runCatching { StampType.valueOf(this) }.getOrElse { StampType.CLOVER }
+private fun String.toStampType(): StampType =
+    runCatching { StampType.valueOf(this) }.getOrElse {
+        Log.w("StatsMapper", "Unknown StampType: $this, falling back to CLOVER")
+        StampType.CLOVER
+    }

-private fun String.toStampColor(): StampColor = runCatching { StampColor.valueOf(this) }.getOrElse { StampColor.GREEN400 }
+private fun String.toStampColor(): StampColor =
+    runCatching { StampColor.valueOf(this) }.getOrElse {
+        Log.w("StatsMapper", "Unknown StampColor: $this, falling back to GREEN400")
+        StampColor.GREEN400
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/network/src/main/java/com/twix/network/model/response/stats/mapper/StatsMapper.kt`
around lines 38 - 40, The String extension functions toStampType and
toStampColor silently swallow valueOf failures via runCatching and return
defaults, making API casing mismatches invisible; update both functions to first
normalize the input (e.g., this.uppercase()) before calling StampType.valueOf /
StampColor.valueOf and add a catch block that logs a warning including the
original value and the default chosen (use the project's logger instance) so any
fallback is observable; keep the default fallback behavior but ensure the log
mentions the function name (toStampType/toStampColor), the raw input, and the
default returned.
core/network/src/main/java/com/twix/network/model/response/stats/model/StatsResponse.kt (1)

9-9: statsGoalsstampColors 리스트 필드에 null 안전성 보완을 고려해보세요.

왜 문제가 되나요? 현재 두 필드 모두 non-nullable List로 선언되어 있습니다. API가 해당 필드를 null로 반환하거나 필드 자체를 누락하면, kotlinx.serialization은 SerializationException을 던져 런타임에 크래시가 발생합니다.

어떻게 개선할 수 있을까요? 서버 계약이 항상 non-null을 보장한다면 현재 구조로 충분하지만, 방어적으로 nullable + 기본값 처리를 고려해볼 수 있습니다.

 `@Serializable`
 data class StatsResponse(
     `@SerialName`("selectedDate") val selectedDate: String,
-    `@SerialName`("statsGoals") val statsGoals: List<StatsGoalResponse>,
+    `@SerialName`("statsGoals") val statsGoals: List<StatsGoalResponse> = emptyList(),
 )
 
 `@Serializable`
 data class ParticipantStatsResponse(
     `@SerialName`("nickname") val nickname: String,
     `@SerialName`("endCount") val endCount: Int,
-    `@SerialName`("stampColors") val stampColors: List<String>,
+    `@SerialName`("stampColors") val stampColors: List<String> = emptyList(),
 )

서버 스펙이 null을 절대 반환하지 않음이 확실하다면 현 구조를 유지해도 무방합니다. 다만 스펙이 바뀌거나 오류 응답에서 필드가 누락될 경우를 대비해 방어 처리 여부를 팀과 논의해보시면 좋겠습니다.

Also applies to: 27-27

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/network/src/main/java/com/twix/network/model/response/stats/model/StatsResponse.kt`
at line 9, Change the collection fields in StatsResponse to be defensive against
missing/null payloads: update the declarations for statsGoals and stampColors to
nullable types with a safe default (e.g., val statsGoals:
List<StatsGoalResponse>? = emptyList() and val stampColors:
List<StampColorResponse>? = emptyList()) so kotlinx.serialization won't throw
when the fields are omitted or null; locate these fields in the StatsResponse
class and apply the same pattern to both.
core/network/src/main/java/com/twix/network/service/StatsService.kt (1)

10-13: 쿼리 파라미터로 YearMonth를 직접 전달하는 방식은 명시성 측면에서 개선할 수 있습니다.

Ktorfit의 @Query 파라미터는 String이 아닌 타입을 toString()으로 직렬화합니다. 현재 코드에서는 YearMonth.toString()"2024-01" 형식의 문자열을 반환하고, 이것이 API 쿼리 파라미터로 전송됩니다. API가 이 형식을 수용하고 있으므로 기능상 문제는 없지만, 직렬화 형식을 코드에서 명시적으로 표현하면 의도를 더 명확하게 드러낼 수 있습니다.

호출부(DefaultStatsRepository)에서 포맷팅된 String을 전달하는 방식이 더 명확하고 예측 가능합니다:

 interface StatsService {
     `@GET`("api/v1/stats")
     suspend fun fetchStats(
-        `@Query`("selectedDate") selectedDate: YearMonth,
+        `@Query`("selectedDate") selectedDate: String,
         `@Query`("status") status: String,
     ): StatsResponse
 }

호출부에서:

statsService.fetchStats(
    selectedDate = yearMonth.toString(), // "yyyy-MM" 형식 명시
    status = status.toApi()
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/network/src/main/java/com/twix/network/service/StatsService.kt` around
lines 10 - 13, Change the fetchStats signature to accept a formatted String
instead of YearMonth so the query serialization is explicit: update the suspend
fun fetchStats(...) declaration in StatsService (replace YearMonth parameter
with String named selectedDate) and update callers such as
DefaultStatsRepository to pass yearMonth.toString() (or a dedicated formatter
producing "yyyy-MM") and leave status mapping (status.toApi()) as-is; ensure
tests/usage sites are adjusted to the new parameter type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@feature/main/src/main/java/com/twix/stats/StatsViewModel.kt`:
- Around line 86-92: Move the freshness checks so the cache is only updated
after verifying the response is the latest and for the current month: compute
isLatestRequest (requestId == latestInProgressRequestId) and isCurrentMonth
(YearMonth.from(currentState.currentDate) == date) first, then if both are true
call inProgressStatsCache[date] = stats and reduce { copy(inProgressStats =
stats) } (or update only the cache when not updating current state if intended),
otherwise skip writing to inProgressStatsCache to avoid older responses
overwriting newer data.

---

Duplicate comments:
In `@data/src/main/java/com/twix/data/repository/DefaultStatsRepository.kt`:
- Around line 28-33: Replace the TODO in DefaultStatsRepository.fetchStatsDetail
with a real implementation that wraps the remote call and mapping in safeApiCall
and returns an AppResult<StatsDetail>; specifically, call the repository's stats
API/client (the same client used elsewhere in this class), invoke the detail
endpoint for the given goalId and date, map the response to the domain
StatsDetail (using the existing mapper function, e.g., toStatsDetail() or
equivalent), and return the result from safeApiCall so errors are captured as
AppResult instead of throwing NotImplementedError.

In `@domain/src/main/java/com/twix/domain/model/enums/StatsStatus.kt`:
- Around line 12-14: The fromApi function in companion object of StatsStatus
currently swallows unknown API values by mapping them to IN_PROGRESS; change it
to either return a dedicated UNKNOWN enum value (add UNKNOWN to StatsStatus) or
throw/log the unexpected value instead of defaulting to IN_PROGRESS so domain
meaning isn't corrupted; specifically update the companion object method fromApi
to detect missing value (valueOf failure) and map to StatsStatus.UNKNOWN or call
process/logger with the raw value and return a safe sentinel, ensuring
references to fromApi, StatsStatus, IN_PROGRESS are adjusted accordingly.

In
`@feature/stats/detail/src/main/java/com/twix/stats/detail/StatsDetailViewModel.kt`:
- Around line 47-56: Restore the disabled initial and month-change detail
fetches: un-comment and call fetchStatsDetail(...) once in the init block (using
argDate?.let { LocalDate.parse(it) }?.let(YearMonth::from) ?: YearMonth.now() to
compute the start YearMonth) and also invoke fetchStatsDetail(yearMonth) inside
the monthChangeFlow collector in collectMonthChangeFlow()/where monthChangeFlow
is collected so that reduceNavArguments(), collectMonthChangeFlow(), and state
updates trigger an actual data load on init and on month changes.

---

Nitpick comments:
In
`@core/network/src/main/java/com/twix/network/model/response/stats/mapper/StatsMapper.kt`:
- Around line 38-40: The String extension functions toStampType and toStampColor
silently swallow valueOf failures via runCatching and return defaults, making
API casing mismatches invisible; update both functions to first normalize the
input (e.g., this.uppercase()) before calling StampType.valueOf /
StampColor.valueOf and add a catch block that logs a warning including the
original value and the default chosen (use the project's logger instance) so any
fallback is observable; keep the default fallback behavior but ensure the log
mentions the function name (toStampType/toStampColor), the raw input, and the
default returned.

In
`@core/network/src/main/java/com/twix/network/model/response/stats/model/StatsResponse.kt`:
- Line 9: Change the collection fields in StatsResponse to be defensive against
missing/null payloads: update the declarations for statsGoals and stampColors to
nullable types with a safe default (e.g., val statsGoals:
List<StatsGoalResponse>? = emptyList() and val stampColors:
List<StampColorResponse>? = emptyList()) so kotlinx.serialization won't throw
when the fields are omitted or null; locate these fields in the StatsResponse
class and apply the same pattern to both.

In `@core/network/src/main/java/com/twix/network/service/StatsService.kt`:
- Around line 10-13: Change the fetchStats signature to accept a formatted
String instead of YearMonth so the query serialization is explicit: update the
suspend fun fetchStats(...) declaration in StatsService (replace YearMonth
parameter with String named selectedDate) and update callers such as
DefaultStatsRepository to pass yearMonth.toString() (or a dedicated formatter
producing "yyyy-MM") and leave status mapping (status.toApi()) as-is; ensure
tests/usage sites are adjusted to the new parameter type.

In
`@feature/main/src/main/java/com/twix/stats/preview/StatsUiStatePreviewProvider.kt`:
- Line 56: Update the preview data so Compose Preview shows the closed-stats UI:
in StatsUiStatePreviewProvider, populate completedStats with representative
sample items instead of emptyList() in the preview states (e.g., inside
defaultState() and the other preview state at the same file). Create a small
list of sample completed stat objects that match the
StatsUiState/EndStatsContent model and assign it to completedStats so
EndStatsContent renders meaningfully in the preview.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d21ed05 and 6c84751.

📒 Files selected for processing (14)
  • core/network/src/main/java/com/twix/network/di/ApiServiceModule.kt
  • core/network/src/main/java/com/twix/network/model/response/stats/mapper/StatsMapper.kt
  • core/network/src/main/java/com/twix/network/model/response/stats/model/StatsResponse.kt
  • core/network/src/main/java/com/twix/network/service/StatsService.kt
  • data/src/main/java/com/twix/data/di/RepositoryModule.kt
  • data/src/main/java/com/twix/data/repository/DefaultStatsRepository.kt
  • data/src/main/java/com/twix/data/repository/FakeStatsRepository.kt
  • domain/src/main/java/com/twix/domain/model/enums/StatsStatus.kt
  • domain/src/main/java/com/twix/domain/repository/StatsRepository.kt
  • feature/main/src/main/java/com/twix/stats/StatsScreen.kt
  • feature/main/src/main/java/com/twix/stats/StatsViewModel.kt
  • feature/main/src/main/java/com/twix/stats/contract/StatsUiState.kt
  • feature/main/src/main/java/com/twix/stats/preview/StatsUiStatePreviewProvider.kt
  • feature/stats/detail/src/main/java/com/twix/stats/detail/StatsDetailViewModel.kt
💤 Files with no reviewable changes (1)
  • data/src/main/java/com/twix/data/repository/FakeStatsRepository.kt

Copy link
Member

@dogmania dogmania 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
Member

Choose a reason for hiding this comment

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

확실히 캐시를 두고 쓰는 게 불러오는 속도가 빨라서 좋네요 근데 지금 캐시 최대 사이즈 같은 정책이 따로 없어서 캐시 사이즈가 늘어날 수도 있을 것 같아요 나중에 최대 사이즈를 정하거나 제한을 추가해주세요

@chanho0908 chanho0908 merged commit 6234583 into develop Feb 25, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants