Skip to content

Conversation

@VelikovPetar
Copy link
Contributor

@VelikovPetar VelikovPetar commented Dec 22, 2025

🎯 Goal

The cannel unread count should not be updated for thread replies (unless the reply is also sent to channel).

🛠 Implementation details

Fix the case where message.parentMessageId != null is increasing the channel unread count.

🎨 UI Changes

Before After
before.mov
after.mov

🧪 Testing

  1. Device 1 - Open channel list
  2. Device 2 - Reply to a thread in a channel where both users are members
  3. The unread count (on Device 1) should not be increased (unless the thread reply is also sent to the channel)

Summary by CodeRabbit

Bug Fixes

  • Fixed an issue where thread replies were incorrectly updating the channel unread count.

✏️ Tip: You can customize this high-level summary in your review settings.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 22, 2025

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.25 MB 5.25 MB 0.00 MB 🟢
stream-chat-android-offline 5.48 MB 5.48 MB 0.00 MB 🟢
stream-chat-android-ui-components 10.60 MB 10.60 MB 0.00 MB 🟢
stream-chat-android-compose 12.81 MB 12.81 MB 0.00 MB 🟢

@VelikovPetar VelikovPetar marked this pull request as ready for review December 22, 2025 13:01
@VelikovPetar VelikovPetar requested a review from a team as a code owner December 22, 2025 13:01
@coderabbitai
Copy link

coderabbitai bot commented Dec 22, 2025

Walkthrough

This pull request fixes an issue where thread replies were incorrectly updating the channel unread count. The updateCurrentUserRead method in ChannelStateLogic is refactored with multiple early-return guards to skip unread count updates under specific conditions, including thread replies not shown in the channel, messages from the current user, and outdated events. The fix is accompanied by expanded test coverage.

Changes

Cohort / File(s) Summary
Documentation
CHANGELOG.md
Added fix entry noting that thread replies no longer incorrectly update the channel unread count.
Logic Implementation
stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogic.kt
Refactored updateCurrentUserRead to introduce per-message processing tracking and multiple early-return guards: skip if already processed, channel muted, thread replies not shown in channel, message from current user, shadowed/banned user, silent message, or outdated local read state. Only updates unread count if no guards apply.
Test Coverage
stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt
Replaced parameterized test with individual test cases covering scenarios: already processed messages, muted channels, thread replies (shown and hidden), messages from current user, shadowed/silent messages, outdated events, and regular messages from others. Added initialization of unread counts and event dates, removed Arguments-driven flow, and added @Suppress annotation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Logic refactoring introduces multiple conditional guard paths that require careful verification of correctness and edge-case handling
  • Test expansion is substantial with diverse scenarios covering new guard conditions, requiring validation that each test correctly asserts expected behavior
  • Review should focus on verifying the guard conditions prevent unread count updates in the correct scenarios and that the per-message processing tracking prevents duplicates

Suggested reviewers

  • aleksandar-apostolov
  • gpunto

Poem

🐰 Thread replies once caused chaos, unread counts would soar,
But now our guards stand sentinel at the logic's door,
Early returns and whispered checks keep the count so true,
While test cases dance and verify each path anew!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the goal, implementation details, and UI changes with videos. However, it omits several required checklist items (CLA signature, assignee, Slack thread, changelog, unit tests, KDocs) that are marked as required in the template. Complete the required checklist items: confirm CLA signature, assign a code owner, start a Slack thread, verify changelog and unit test coverage, and ensure KDocs are updated.
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and directly describes the main fix: preventing thread replies from incorrectly updating the channel unread count, which matches the core change in the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bug/AND-968_fix_thread_replies_increasing_unread_count

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: 0

🧹 Nitpick comments (1)
CHANGELOG.md (1)

35-38: Clarify that the unread update was incorrect (minor wording nit).

To better reflect the bug being fixed, consider wording like “Fix thread replies incorrectly updating channel unread count” so it’s clear this was an erroneous increment, not a neutral behavior change.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 686d5c3 and 483d753.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogic.kt
  • stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{kt,kts}

📄 CodeRabbit inference engine (AGENTS.md)

Format and apply Kotlin style with Spotless (4 spaces, no wildcard imports, licence headers)

Files:

  • stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt
  • stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogic.kt
**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

**/*.kt: Use @OptIn annotations explicitly; avoid suppressions unless documented
Document public APIs with KDoc, including thread expectations and state notes

Files:

  • stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt
  • stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogic.kt
**/src/test/**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

**/src/test/**/*.kt: Use backtick test names (for example: fun message list filters muted channels()) for readability
Use deterministic tests with runTest + virtual time for concurrency-sensitive logic (uploads, sync, message state)
Keep helper extensions private/internal in test files

Files:

  • stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt
🧠 Learnings (5)
📚 Learning: 2025-12-17T15:00:07.506Z
Learnt from: CR
Repo: GetStream/stream-chat-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T15:00:07.506Z
Learning: Applies to **/stream-chat-android-ui-components/**/*Test.kt : Record Shot baselines when behaviour changes in XML kit UI tests

Applied to files:

  • stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt
📚 Learning: 2025-12-17T15:00:07.506Z
Learnt from: CR
Repo: GetStream/stream-chat-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T15:00:07.506Z
Learning: Applies to **/src/test/**/*.kt : Use deterministic tests with `runTest` + virtual time for concurrency-sensitive logic (uploads, sync, message state)

Applied to files:

  • stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt
📚 Learning: 2025-12-17T15:00:07.506Z
Learnt from: CR
Repo: GetStream/stream-chat-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T15:00:07.506Z
Learning: Applies to **/stream-chat-android-compose/**/*Test.kt : Add Paparazzi snapshots for Compose UI regressions and run `verifyPaparazziDebug`

Applied to files:

  • stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt
📚 Learning: 2025-12-17T15:00:07.506Z
Learnt from: CR
Repo: GetStream/stream-chat-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T15:00:07.506Z
Learning: Applies to **/src/test/**/*.kt : Use backtick test names (for example: `fun `message list filters muted channels`()`) for readability

Applied to files:

  • stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt
📚 Learning: 2025-12-17T15:00:07.506Z
Learnt from: CR
Repo: GetStream/stream-chat-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T15:00:07.506Z
Learning: Applies to **/src/test/**/*.kt : Keep helper extensions private/internal in test files

Applied to files:

  • stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt
🧬 Code graph analysis (1)
stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt (1)
stream-chat-android-core/src/testFixtures/kotlin/io/getstream/chat/android/Mother.kt (4)
  • randomChannelUserRead (485-501)
  • randomString (94-98)
  • randomMessage (302-399)
  • randomUser (182-228)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test compose (2)
  • GitHub Check: Test compose (1)
  • GitHub Check: Test compose (0)
  • GitHub Check: Debug build
  • GitHub Check: Unit tests
🔇 Additional comments (16)
stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogic.kt (7)

866-870: Good use of deduplication cache.

The early-exit guard for already-processed messages prevents duplicate unread count updates. The cache-based approach is efficient.


871-882: Thread reply filtering logic looks correct.

The guard correctly skips unread count updates for thread replies not shown in the channel (parentId != null && !showInChannel). This addresses the core issue described in the PR objectives.


883-898: LGTM: Guards for current user and message types.

The guards for messages from the current user, shadowed messages, and silent messages are correctly implemented. These ensure that only relevant messages increment the unread count.


899-904: Outdated event guard prevents race conditions.

This guard correctly prevents stale events from overwriting more recent local state. This is a good defensive measure against out-of-order event delivery.


905-914: Unread count update logic is sound.

The method correctly increments the unread count and updates the lastReceivedEventDate only when all guards pass. The final processedMessageIds.put() ensures idempotency.


878-878: No issues found. The property name message.parentId is correct for the Message model in the Stream Chat Android SDK.


78-78: Cache size of 100 is adequate for per-channel message deduplication.

The processedMessageIds cache is scoped per-channel instance and serves to deduplicate read event processing. A cache size of 100 entries is reasonable and consistent with common Android practices. Even if cache entries are evicted during rapid message arrival, the resulting duplicate read update is idempotent and has minimal performance impact. No production issues or overflow scenarios have been reported in the codebase.

stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt (9)

72-72: Appropriate use of suppression annotation.

The @Suppress("LargeClass") annotation is justified given the comprehensive test coverage added. The test class has grown due to the expanded guard conditions in the implementation.


443-473: Excellent test for deduplication logic.

The test correctly verifies that processing the same message twice only increments the unread count once. This validates the processedMessageIds cache behavior.

As per coding guidelines, the backtick test name is readable and descriptive.


476-503: LGTM: Muted channel test.

The test correctly verifies that muted channels don't update unread counts. The setup and assertions are clear.


505-533: Critical test case for the PR objective.

This test validates the core fix: thread replies not shown in channel (parentId != null && showInChannel = false) should not increment the unread count. This directly addresses the bug described in the PR.


535-620: Comprehensive coverage of message type guards.

The tests for current user messages, shadowed messages, and silent messages are well-structured and verify the correct behavior. Each test follows the same pattern for consistency.


622-649: Good test for race condition protection.

This test verifies that outdated events (older than the current read state) don't incorrectly update the unread count. This prevents issues with out-of-order event delivery.


651-683: Happy path test validates correct increment.

This test verifies that a regular message from another user correctly increments the unread count and updates the lastReceivedEventDate. The expected state is explicitly constructed and verified.


685-718: Important edge case: thread replies shown in channel.

This test verifies that thread replies with showInChannel = true DO increment the unread count, which is the correct behavior. This distinguishes between the two thread reply scenarios (shown vs. not shown in channel).


720-739: Defensive test for null read state.

This test ensures the method handles the edge case where no current read state exists without crashing. The method correctly skips the update when currentRead is null.

@sonarqubecloud
Copy link

@gpunto gpunto merged commit a728daa into develop Dec 23, 2025
13 checks passed
@gpunto gpunto deleted the bug/AND-968_fix_thread_replies_increasing_unread_count branch December 23, 2025 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants