Skip to content

Refresh Stats on resume so the latest data appears automatically#23112

Open
jkmassel wants to merge 3 commits into
trunkfrom
jkmassel/screen-not-reloading-stale-data
Open

Refresh Stats on resume so the latest data appears automatically#23112
jkmassel wants to merge 3 commits into
trunkfrom
jkmassel/screen-not-reloading-stale-data

Conversation

@jkmassel

@jkmassel jkmassel commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Description

Should resolve https://linear.app/a8c/issue/CMM-2141.

Summary

  • Stats now re-fetch when you return to the screen, so newly-available or previously-stale data appears without a manual pull-to-refresh — and existing rows stay on screen during the refresh instead of blanking.

Root cause

The Subscribers/Insights/Traffic stats use cases are @Singleton (StatsModule BLOCK_SUBSCRIBERS_USE_CASESSUBSCRIBERS_USE_CASE, and equivalents), so BaseStatsUseCase.domainModel lives for the entire app process. Nothing refetched on re-display:

  • StatsListFragment.onResume() didn't refresh — only pull-to-refresh did.
  • StatsListViewModel.start() short-circuits on isInitialized, and its loadData(refresh = false) no-ops against an already-SUCCESS singleton.

So once a stale/incomplete result was loaded — e.g. during a server-side email/subscriber stats aggregation lag (CMM-2141), where a recently-published post is temporarily missing from stats/emails/summary — that result was pinned in memory and re-shown on every return with no network call. Server-side recovery never surfaced until the user pulled to refresh or the process was killed.

Fix

  1. Refresh on resumeStatsListFragment.onResume() now calls viewModel.onRefresh():

    if (::viewModel.isInitialized) { viewModel.onRefresh() }

    onResume fires on every foreground / tab return, so the refresh is deliberately non-forced and leans on the store-level STALE_PERIOD (5 min, StatsRequestSqlUtils) to throttle to at most one network request per 5 minutes per stat type. Applies to all sections hosted by StatsListFragment.

  2. Keep rows visible during the refresh — a refresh=true fetch drives blocks through a LOADING state for the network round-trip. mapSubscribers/mapStatsWithOverview rendered LOADING from stateData only, and mapInsights used stateData ?: data — but stateData (the placeholder from buildLoadingItem()) is always non-null, so all three discarded the already-loaded rows and blanked the block to a bare title during every network refresh. They now prefer data (data ?: stateData ?: listOf()), matching StatsViewAllViewModel: first load still shows the placeholder, but refreshing loaded data keeps the rows on screen. (Within-5-min resumes are collapsed entirely by the 50 ms updateState debounce, so there's no visible change at all.)

Boundary — what this does NOT fix

This does not touch the server-side lag itself (CMM-2141 root cause): if the user returns while stats/emails/summary is still missing the post, the refetch returns the same incomplete list. It shrinks the client staleness window from "until manual refresh / process death" to "next resume, ≤5 min throttled," so once the server recovers the app reflects it automatically.

Testing instructions

Stats refresh on return:

  1. Open Stats → Subscribers on a site with email/newsletter stats.
  2. Note the Emails list.
  3. Background the app (or switch to another Stats tab and back) and wait >5 minutes.
  4. Return to Stats → Subscribers.
  • Verify the list re-fetches (updated data appears) without a manual pull-to-refresh.
  • Verify existing rows stay on screen during the refresh (no blank/flash to a bare title).
  • Verify returning again within 5 minutes shows no visible reload (served from cache).

No regression to pull-to-refresh:

  1. On any Stats tab, pull to refresh.
  • Verify it still forces a fresh fetch, and rows remain visible during the refresh.

Unit tests:

  • UiModelMapperTestmapSubscribers keeps loaded rows visible while a block is refreshing + the first-load placeholder case.

Related

@dangermattic

dangermattic commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator
1 Warning
⚠️ This PR is assigned to the milestone 27.0. This milestone is due in less than 4 days.
Please make sure to get it merged by then or assign it to a milestone with a later deadline.

Generated by 🚫 Danger

The Subscribers, Insights, and Traffic stats use cases are @singleton, so their
in-memory domainModel lives for the whole app process. Nothing refetched when the
screen was re-displayed — onResume didn't refresh, and start() no-ops against an
already-SUCCESS singleton. Once a stale or incomplete result was loaded (e.g.
during a server-side stats aggregation lag), it was pinned and re-shown on every
return with no network call, so server-side recovery never surfaced without a
manual pull-to-refresh or a cold start.

Trigger the existing non-forced refresh from StatsListFragment.onResume(). The
store-level STALE_PERIOD (5 min) throttles this to at most one network request per
5 minutes, and the 50ms updateState debounce keeps within-window resumes from
flashing a loading state.
@jkmassel
jkmassel force-pushed the jkmassel/screen-not-reloading-stale-data branch from 2197627 to fa3e1dc Compare July 16, 2026 21:52
@wpmobilebot

wpmobilebot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

App Icon📲 You can test the changes from this Pull Request in Jetpack Android by scanning the QR code below to install the corresponding build.

App NameJetpack Android
Build TypeDebug
Versionpr23112-9e41c21
Build Number1498
Application IDcom.jetpack.android.prealpha
Commit9e41c21
Installation URL0ln740p4fsie0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot

wpmobilebot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

App Icon📲 You can test the changes from this Pull Request in WordPress Android by scanning the QR code below to install the corresponding build.

App NameWordPress Android
Build TypeDebug
Versionpr23112-9e41c21
Build Number1498
Application IDorg.wordpress.android.prealpha
Commit9e41c21
Installation URL1jnljj3aludf0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

…ceholder

The onResume refresh (and pull-to-refresh) drive blocks through a LOADING state
during the network round-trip. mapSubscribers and mapStatsWithOverview rendered
LOADING from stateData only (the loading placeholder = a bare title), and
mapInsights used `stateData ?: data` — but stateData is always non-null
(buildLoadingItem returns a title), so all three discarded the already-loaded
rows and blanked the blocks for the duration of the fetch.

Prefer data over the placeholder on LOADING (data ?: stateData ?: listOf()),
matching StatsViewAllViewModel. First load still shows the placeholder (no data
yet); a refresh of already-loaded data keeps the rows on screen. Add regression
tests for mapSubscribers.
@jkmassel
jkmassel requested review from adalpari and nbradbury July 17, 2026 02:30
@jkmassel jkmassel self-assigned this Jul 17, 2026
@jkmassel jkmassel added this to the 27.0 milestone Jul 17, 2026
@jkmassel
jkmassel marked this pull request as ready for review July 17, 2026 02:30
@wpmobilebot

Copy link
Copy Markdown
Contributor

🤖 Build Failure Analysis

This build has failures. Claude has analyzed them - check the build annotations for details.

// path, so StatsRequestSqlUtils.STALE_PERIOD throttles it to at most one network request per
// 5 minutes; resumes within that window are served from cache.
if (::viewModel.isInitialized) {
viewModel.onRefresh()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⛏️ The first run will be making two refresh calls now. One in viewmodel.start() called from onViewCreated()and one in onResume()

On first display the initial start() load (from onViewCreated) and the onResume
refresh both fire and race to the network before either records the StatsRequest
timestamp, so STALE_PERIOD doesn't dedup them. Add a per-use-case AtomicBoolean
guard in BaseStatsUseCase.fetch: a second concurrent non-forced fetch is skipped
while one is in flight. A forced refresh (pull-to-refresh) still proceeds so it
can bypass STALE_PERIOD. Keeps offscreen tab preloading (start() unchanged) while
removing the first-load double-fetch; also covers other load races.
@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 57.14286% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 37.64%. Comparing base (184bd2b) to head (9e41c21).
⚠️ Report is 2 commits behind head on trunk.

Files with missing lines Patch % Lines
...ss/android/ui/stats/refresh/lists/UiModelMapper.kt 28.57% 4 Missing and 1 partial ⚠️
...i/stats/refresh/lists/sections/BaseStatsUseCase.kt 85.71% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##            trunk   #23112   +/-   ##
=======================================
  Coverage   37.63%   37.64%           
=======================================
  Files        2343     2343           
  Lines      127290   127297    +7     
  Branches    17655    17658    +3     
=======================================
+ Hits        47903    47917   +14     
+ Misses      75464    75451   -13     
- Partials     3923     3929    +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@adalpari adalpari left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@nbradbury
nbradbury removed their request for review July 20, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants