Conversation
…ats-top-authors-card # Conflicts: # WordPress/src/main/java/org/wordpress/android/ui/newstats/datasource/StatsDataSourceImpl.kt # gradle/libs.versions.toml
# Conflicts: # WordPress/src/main/java/org/wordpress/android/ui/newstats/datasource/StatsDataSourceImpl.kt # gradle/libs.versions.toml
|
Claude finished @adalpari's task in 1m 45s —— View job Code Review CompleteI've completed a comprehensive review of this PR. Overall, the implementation effectively solves the duplicate API calls and unnecessary data fetching issues. The approach is sound, but I've identified several areas for improvement. ✅ Strengths
🔍 Issues Found1. Critical: Race Condition in ViewsStatsViewModel (ViewsStatsViewModel.kt:101-105)The fun loadDataIfNeeded() {
if (loadedPeriod == currentPeriod) return
loadedPeriod = currentPeriod
loadData()
}Problem: If
Solution: Track loading state properly: private var loadingPeriod: StatsPeriod? = null
private var loadedPeriod: StatsPeriod? = null
fun loadDataIfNeeded() {
val targetPeriod = currentPeriod
if (loadedPeriod == targetPeriod || loadingPeriod == targetPeriod) return
loadingPeriod = targetPeriod
loadData()
}
private suspend fun loadDataInternal(site: SiteModel) {
try {
// ... existing code ...
loadedPeriod = currentPeriod
} finally {
loadingPeriod = null
}
}2. Bug: TodaysStatsViewModel Doesn't Update on Config Changes (TodaysStatsViewModel.kt:42-46)Unlike other ViewModels, fun loadDataIfNeeded() {
if (isInitialLoadDone) return // ❌ Always returns after first load
isInitialLoadDone = true
loadData()
}Problem: If a user hides the "Today's Stats" card and then shows it again later, Solution: Either:
private var hasLoadedData = false
fun loadDataIfNeeded() {
if (hasLoadedData && _uiState.value is TodaysStatsCardUiState.Loaded) return
hasLoadedData = true
loadData()
}3. Potential Memory Leak: Unconstrained Flow Collection (NewStatsViewModel.kt:66-74)The private fun observeConfigurationChanges() {
viewModelScope.launch {
cardConfigurationRepository.configurationFlow.collect { pair ->
if (pair != null && pair.first == siteId) {
updateFromConfiguration(pair.second)
}
}
}
}Problem:
Solution: Use private fun observeConfigurationChanges() {
viewModelScope.launch {
cardConfigurationRepository.configurationFlow.collect { pair ->
val currentSiteId = selectedSiteRepository.getSelectedSite()?.siteId ?: 0L
if (pair != null && pair.first == currentSiteId) {
updateFromConfiguration(pair.second)
}
}
}
}4. Code Quality: Inconsistent loadDataIfNeeded() ImplementationsThree different patterns are used across ViewModels: Pattern A (TodaysStatsViewModel): private var isInitialLoadDone = false
fun loadDataIfNeeded() {
if (isInitialLoadDone) return
isInitialLoadDone = true
loadData()
}Pattern B (ViewsStatsViewModel): private var loadedPeriod: StatsPeriod? = null
fun loadDataIfNeeded() {
if (loadedPeriod == currentPeriod) return
loadedPeriod = currentPeriod
loadData()
}Pattern C (MostViewedViewModel, CountriesViewModel, AuthorsViewModel): private var isInitialLoadDone = false
fun onPeriodChanged(period: StatsPeriod) {
if (isInitialLoadDone && currentPeriod == period) return
isInitialLoadDone = true
currentPeriod = period
loadData()
}Recommendation: Standardize on a single pattern. Pattern B is most correct for period-based cards, but needs the race condition fix. Pattern A needs to be fixed per issue #2. 5. Performance: Multiple Parallel Loads in MostViewedViewModel (MostViewedViewModel.kt:171-178)private suspend fun loadDataInternal(siteId: Long) {
// Load both data sources in parallel
viewModelScope.launch {
loadDataForSourceInternal(siteId, MostViewedDataSource.POSTS_AND_PAGES)
}
viewModelScope.launch {
loadDataForSourceInternal(siteId, MostViewedDataSource.REFERRERS)
}
}Issue: Launching with
Better approach: private suspend fun loadDataInternal(siteId: Long) = coroutineScope {
launch { loadDataForSourceInternal(siteId, MostViewedDataSource.POSTS_AND_PAGES) }
launch { loadDataForSourceInternal(siteId, MostViewedDataSource.REFERRERS) }
}💡 Suggestions6. Testing Gap: Missing Test for cardsToLoad Preventing Premature FetchThe core bug fix (preventing default card fetches before config loads) lacks a specific test. Add: @Test
fun `when ViewModel is created, cardsToLoad starts empty`() = test {
// Don't call initViewModel() to avoid the config load
viewModel = NewStatsViewModel(
selectedSiteRepository,
cardConfigurationRepository,
networkUtilsWrapper
)
// Should start empty to prevent premature fetches
assertThat(viewModel.cardsToLoad.value).isEmpty()
}7. Consider: Extract Visibility Dispatch PatternThe sealed class CardAction {
object Load : CardAction()
object Refresh : CardAction()
data class PeriodChanged(val period: StatsPeriod) : CardAction()
}
fun List<StatsCardType>.dispatchToVisibleCards(
action: CardAction,
todaysStatsViewModel: TodaysStatsViewModel,
viewsStatsViewModel: ViewsStatsViewModel,
// ... other ViewModels
) {
when (action) {
is CardAction.Load -> {
if (TODAYS_STATS in this) todaysStatsViewModel.loadDataIfNeeded()
// ...
}
is CardAction.Refresh -> {
if (TODAYS_STATS in this) todaysStatsViewModel.refresh()
// ...
}
is CardAction.PeriodChanged -> {
// Only period-sensitive cards
if (VIEWS_STATS in this) viewsStatsViewModel.loadDataIfNeeded()
// ...
}
}
}📊 SummaryCritical Issues: 3 (race condition, reload bug, flow collection bug) Recommendation: Address critical issues #1-3 before merging. Issues #4-5 can be addressed in a follow-up if time-constrained. |
Generated by 🚫 Danger |
This is correct. Today's stats card will always show Today's period. So, no need to refresh |
|
|
|
|
# Conflicts: # WordPress/src/main/java/org/wordpress/android/ui/newstats/NewStatsActivity.kt # WordPress/src/main/java/org/wordpress/android/ui/newstats/authors/AuthorsViewModel.kt # WordPress/src/main/java/org/wordpress/android/ui/newstats/countries/CountriesViewModel.kt # WordPress/src/test/java/org/wordpress/android/ui/newstats/authors/AuthorsViewModelTest.kt # gradle/libs.versions.toml
|
Claude finished @adalpari's task in 3m 58s —— View job Code Review
OverviewThe PR's core approach is sound: gating all data-fetching calls behind visibility checks and a ✅ Improvements Since Previous Review
🔍 Issues Found1.
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## trunk #22586 +/- ##
==========================================
+ Coverage 38.16% 38.18% +0.02%
==========================================
Files 2254 2254
Lines 113362 113442 +80
Branches 15791 15802 +11
==========================================
+ Hits 43259 43315 +56
- Misses 66516 66534 +18
- Partials 3587 3593 +6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@adalpari I removed all cards except "Posts & Pages":
I then did a pull-to-refresh, and the inspector showed network requests for both top posts and referrers:
Is this expected? |
|
@adalpari I did a Claude review locally and a few issues were reported (see attached MD). Should any of these be addressed? |
This is odd, the review is talking about a totally different PR:
And it's reporting issues about code or classes ( I've checked the reports anyway, and there might be a couple of points that make sense. I'll take it into account in a different PR. |
Thanks for noting it! No, it's not expected. It's because they started being the same cads with just a different source. When I split them into two different cards, I missed this callback. I'll fix it. |
|
@nbradbury Fixed here: 296eb23 |
|
That's odd, but not the first time I've seen it. I wonder if Claude gets confused by the multiple |
That fix is good! I'll approve this so you can merge when ready. |







Description
Prior to this PR, all card ViewModels loaded data unconditionally on creation regardless of the user's card configuration, wasting network bandwidth. This PR gates all API-triggering calls (period changes, network reconnection, retry, pull-to-refresh) with visibility checks so hidden cards never fetch data.
In addition a duplication call issue has been solved. There was a race condition where te cards were calling the endpoints twice (2x" because of default config loading. Now we are only calling each endpoint once: one for the current period, and one for the previous one to compate data.
Key changes:
init { loadData() }from card ViewModels (TodaysStats,ViewsStats,MostViewed,Countries,Authors) — data loading is now driven by the composablecardsToLoadflow toNewStatsViewModelthat starts empty and only populates after the real configuration loads, preventing premature fetches with the default cardset
isPeriodInitializedtoViewsStatsViewModelto prevent double-fetching when the persisted period is restored asynchronouslyloadDataIfNeeded()idempotent methods toTodaysStatsViewModelandViewsStatsViewModeldispatchToVisibleCardshelper andloadVisibleCardslambda to deduplicate the repeated visibility-gated dispatch logic across 4 call sitesTesting instructions
Only visible cards should trigger API calls:
Verify only the visible cards endpoints are called (current + previous period = 2 calls)
Verify no other card endpoints are called
some test card changes and verify they work as expected