Skip to content

Fix scroll jump when returning to a channel after WS reconnect#6234

Open
VelikovPetar wants to merge 5 commits intov7from
bug/AND-1113_fix_channel_jumps_after_coming_back_from_background
Open

Fix scroll jump when returning to a channel after WS reconnect#6234
VelikovPetar wants to merge 5 commits intov7from
bug/AND-1113_fix_channel_jumps_after_coming_back_from_background

Conversation

@VelikovPetar
Copy link
Contributor

@VelikovPetar VelikovPetar commented Mar 10, 2026

Goal

When the user scrolls up in a channel, puts the app in the background (WS disconnects), and returns, updateDataForChannel() was unconditionally calling state.setMessages() — a full list replacement. This destroyed the older messages the user had loaded and caused a visible scroll jump to the bottom.

Implementation

ChannelStateImpl — Added upsertCachedLatestMessages(messages): merges incoming messages into _cachedLatestMessages using the existing mergeSorted infrastructure, capped at CACHED_LATEST_MESSAGES_LIMIT (25). This lets reconnect logic refresh the "jump to latest" cache without touching the user's active scroll position.

ChannelLogicImpl.updateDataForChannel() — Replaced the unconditional setMessages() with a 4-branch when:

Condition Action
existingMessages.isEmpty() or shouldRefreshMessages setMessages() — initial load / explicit refresh (existing behaviour)
insideSearch == true upsertCachedLatestMessages() — user's window is already trimmed; refresh the "jump to latest" cache only
gap detected (incoming oldest > existing newest, no ID overlap) upsertCachedLatestMessages() + setInsideSearch(true) + setEndOfNewerMessages(false)
no gap (contiguous / overlap) upsertMessages() — normal reconnect, scroll position preserved

🎨 UI Changes

Before After
jump-before.mp4
jump-after-f.mp4

Testing

  • Unit tests added in ChannelLogicImplTest covering all four branches.
  • Unit tests added in ChannelStateImplMessagesTest covering upsertCachedLatestMessages (empty, all-filtered, normal merge).
  • Manual: open channel → scroll up → background → foreground. Scroll position is preserved; no jump.
  • Manual (gap): open channel → scroll up → go offline → 30+ messages sent → come back online. Position preserved, "jump to new messages" indicator appears.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved message consolidation logic to handle gaps between message batches more reliably.
    • Enhanced support for message search scenarios with better caching of recent messages.
    • Refined message state updates to intelligently merge incoming messages based on contiguity and search context.
  • Tests

    • Added comprehensive test coverage for message consolidation behavior and gap detection scenarios.

Co-Authored-By: Claude <noreply@anthropic.com>
@VelikovPetar VelikovPetar added the pr:bug Bug fix label Mar 10, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 10, 2026

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled.

🎉 Great job! This PR is ready for review.

@VelikovPetar VelikovPetar marked this pull request as ready for review March 10, 2026 11:22
@VelikovPetar VelikovPetar requested a review from a team as a code owner March 10, 2026 11:22
@github-actions
Copy link
Contributor

github-actions bot commented Mar 10, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.25 MB 5.70 MB 0.45 MB 🟡
stream-chat-android-ui-components 10.60 MB 11.00 MB 0.40 MB 🟡
stream-chat-android-compose 12.81 MB 12.03 MB -0.78 MB 🚀

@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 78f22efa-6d5c-439a-9da0-3acdf390a30e

📥 Commits

Reviewing files that changed from the base of the PR and between d3accaa and 5f3cd6f.

📒 Files selected for processing (4)
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/logic/channel/internal/ChannelLogicImpl.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateImpl.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/plugin/logic/channel/internal/ChannelLogicImplTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateImplMessagesTest.kt

Walkthrough

This change refactors the channel message update logic to intelligently handle message gaps and discontinuities. It introduces gap detection between current and incoming messages, implements conditional refresh strategies based on state conditions, and adds a cached latest messages feature to preserve messages when gaps are detected.

Changes

Cohort / File(s) Summary
Core Message Update Logic
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/logic/channel/internal/ChannelLogicImpl.kt
Modified updateDataForChannel to replace simple message replacement with multi-branch consolidation logic. Added gap detection via hasGap() helper that checks for non-overlapping messages by createdAt order and ID. Behavior now varies based on shouldRefreshMessages flag, search state, and detected gaps. Updated updateStateFromDatabase to pass shouldRefreshMessages = true instead of query.shouldRefresh.
Cached Latest Messages State
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateImpl.kt
Added new public method upsertCachedLatestMessages(messages: List<Message>) that merges incoming messages into _cachedLatestMessages using sorted merge by ID, filtering ignored messages, and capping to CACHED_LATEST_MESSAGES_LIMIT entries.
Logic Test Coverage
stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/plugin/logic/channel/internal/ChannelLogicImplTest.kt
Added four test scenarios: contiguous message upsert, gap detection with caching, inside-search refresh behavior, and force refresh override. Tests verify correct method invocations for each control flow branch.
State Test Coverage
stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateImplMessagesTest.kt
Added test suite (duplicated) for upsertCachedLatestMessages with scenarios covering empty lists, fully filtered messages, and message merging into cache with size trimming.

Sequence Diagram

sequenceDiagram
    participant Client
    participant ChannelLogic
    participant ChannelState
    participant MessageCache as Cached<br/>Messages

    Client->>ChannelLogic: updateStateFromDatabase()
    ChannelLogic->>ChannelLogic: shouldRefreshMessages = true
    ChannelLogic->>ChannelLogic: hasGap(current, incoming)?

    alt Gap Detected
        ChannelLogic->>MessageCache: upsertCachedLatestMessages(incoming)
        MessageCache->>MessageCache: mergeSorted by ID
        MessageCache->>MessageCache: trim to LIMIT
        ChannelLogic->>ChannelState: setInsideSearch(true)
        ChannelLogic->>ChannelState: setEndOfNewerMessages(false)
    else Inside Search & No Gap
        ChannelLogic->>MessageCache: upsertCachedLatestMessages(incoming)
    else Force Refresh (shouldRefreshMessages=true)
        ChannelLogic->>ChannelState: setMessages(incoming)
    else Contiguous Messages
        ChannelLogic->>ChannelState: upsertMessages(incoming)
        ChannelLogic->>ChannelState: setEndOfOlderMessages(based on limit)
    end

    ChannelState-->>Client: state updated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A gap appeared in messages sent,
So clever logic filled the dent!
Cached the latest, closed the breach,
Now messages flow, each in reach. ✨📬

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% 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 The title 'Fix scroll jump when returning to a channel after WS reconnect' directly and clearly summarizes the main change: preventing a scroll jump issue during WebSocket reconnection.
Description check ✅ Passed The description covers the required sections: Goal (explaining the problem), Implementation (detailing changes to both ChannelStateImpl and ChannelLogicImpl with a clear branch table), UI Changes (with before/after videos), and Testing (unit test details and manual testing steps).

✏️ 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 bug/AND-1113_fix_channel_jumps_after_coming_back_from_background

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.

…fter_coming_back_from_background' into bug/AND-1113_fix_channel_jumps_after_coming_back_from_background
@sonarqubecloud
Copy link

// refresh the "jump to latest" cache with the server's current latest page.
state.upsertCachedLatestMessages(sortedMessages)
}
hasGap(currentMessages, sortedMessages) -> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just for me to understand: this check for a gap between current and incoming, in that order, right?. What if there's a gap the other way around, i.e. all incoming are older than all current, is that a valid case?

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

Labels

pr:bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants