Skip to content

fix(conversation-list): keep showing cached conversations while paging reloads after navigation#4834

Closed
MohamadJaara wants to merge 5 commits into
developfrom
mo/feat/conv-list-snapshot-cache-on-reload
Closed

fix(conversation-list): keep showing cached conversations while paging reloads after navigation#4834
MohamadJaara wants to merge 5 commits into
developfrom
mo/feat/conv-list-snapshot-cache-on-reload

Conversation

@MohamadJaara
Copy link
Copy Markdown
Member

What's new in this PR?

Issues

After navigating away from the conversation list (e.g. opening a conversation) and back, if the underlying data had changed while the user was away, the list area briefly flashed:

  • An empty list area, or
  • The loading shimmer (LoadingListContent)

before the conversations re-appeared. This broke visual continuity and scroll-state expectations on back-navigation.

This is the residual flicker the previous PR (#) flagged but could not fully eliminate.

Causes

paging-compose 3.3.x changed collectAsLazyPagingItems so that it emits an empty LazyPagingItems on the very first composition before the cached PagingData replays. When cachedIn is invalidated upstream — which happens any time conversation activity occurs while the user is on another screen — the new LazyPagingItems instance starts with itemCount == 0 and loadState.refresh == Loading until the new pages finish loading.

The previous PR mitigated this for the no-data-change case by adding shareIn on top of cachedIn so the upstream subscriber stays alive, but the consumer-side LazyPagingItems is still rebuilt on every composition and there was no in-memory source for the screen to fall back to during the empty frame.

Solutions

  • ConversationListViewModel now exposes itemSnapshotCache: StateFlow<PersistentList<ConversationItemType>>. It lives in viewModelScope, so it survives back-stack navigation and is scoped per ConversationsSource (Main, Archive, Favorites…).
  • ConversationsScreenContent feeds the cache from LazyPagingItems.itemSnapshotList via snapshotFlow, filtering out empty snapshots so the transient empty frame never overwrites the cache.
  • New ConversationList(conversationItemsSnapshot: ImmutableList<ConversationItemType>, …) overload renders from a plain list using the same keys, content types, and ConversationItemFactory callbacks as the paged variant.
  • The branching in ConversationsScreenContent is now:
    1. Live items present → paged ConversationList.
    2. Reloading and cache non-empty → snapshot ConversationList (new bridge — eliminates the flash).
    3. Loading and no cache → skeleton (first-ever visit only).
    4. Empty + search active → search empty state.
    5. Empty → "no conversations".

Dependencies

Needs releases with:

  • # (adds shareIn to the conversation paging flow and simplifies collectAsLazyPagingItemsWithLifecycle). This PR is built on top of those changes.

Testing

Test Coverage

  • I have added automated test to this contribution

Added given snapshot cache update, when reading itemSnapshotCache, then it reflects the latest value to ConversationListViewModelTest.

How to Test

Prerequisite: at least one conversation in the list.

  1. Open the app on the conversation list — confirm the loading skeleton renders on the first cold start.
  2. Open any conversation.
  3. While on the conversation screen, trigger an upstream data change (e.g. send/receive a message in this or another conversation, or wait for any presence/last-message update).
  4. Tap back to the conversation list.
  5. Expected: the conversation list is visible immediately with the previously-seen items, then updates in place with the latest data. No skeleton flash, no blank/empty-state flash, scroll position preserved.
  6. Repeat for: Archive, Favorites, and any custom folders — each list has its own cache.
  7. Search edge case: type a search query, open a result, navigate back — the cached snapshot may briefly show pre-search items before the search results re-apply. Confirm this resolves within a frame.

Notes

  • The snapshot cache is scoped to each ConversationListViewModel instance (keyed by ConversationsSource), so each list flavor has its own fallback and they don't bleed into one another.
  • Both ConversationList overloads share rendering logic but are intentionally not deduplicated yet — the paged variant uses LazyPagingItems.itemKey/itemContentType helpers that don't apply to a plain list. Happy to extract a shared LazyListScope extension as a follow-up if reviewers prefer.
  • No new dependencies; uses the kotlinx.collections.immutable and androidx.lifecycle.compose already present in the project.

@MohamadJaara MohamadJaara force-pushed the mo/feat/conv-list-snapshot-cache-on-reload branch from fb51dd2 to 30a84bb Compare May 19, 2026 08:51
@codecov
Copy link
Copy Markdown

codecov Bot commented May 19, 2026

Codecov Report

❌ Patch coverage is 76.00000% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 51.19%. Comparing base (a0e973d) to head (5ae547d).

Files with missing lines Patch % Lines
...ome/conversationslist/ConversationListViewModel.kt 86.36% 1 Missing and 2 partials ⚠️
...me/conversationslist/ConversationsScreenContent.kt 0.00% 3 Missing ⚠️

❌ Your patch check has failed because the patch coverage (76.00%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #4834      +/-   ##
===========================================
+ Coverage    51.16%   51.19%   +0.03%     
===========================================
  Files          611      612       +1     
  Lines        21139    21163      +24     
  Branches      3399     3402       +3     
===========================================
+ Hits         10816    10835      +19     
- Misses        9308     9312       +4     
- Partials      1015     1016       +1     
Files with missing lines Coverage Δ
...ome/conversationslist/ConversationListViewModel.kt 40.47% <86.36%> (+5.55%) ⬆️
...me/conversationslist/ConversationsScreenContent.kt 0.00% <0.00%> (ø)

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a0e973d...5ae547d. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@pull-request-size pull-request-size Bot added size/M and removed size/S labels May 19, 2026
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
14.8% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant