Skip to content

Support local unread counts for channels with read events disabled - #6609

Merged
gpunto merged 28 commits into
developfrom
feat/local-unread-count
Aug 7, 2026
Merged

Support local unread counts for channels with read events disabled#6609
gpunto merged 28 commits into
developfrom
feat/local-unread-count

Conversation

@gpunto

@gpunto gpunto commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Goal

Closes AND-1358

Provide an unread mechanism for channels where server-side read events are disabled (e.g. livestreams). Android port of stream-chat-swift#4102.

Adds an opt-in ChatClientConfig.isLocalUnreadCountEnabled flag (default false). When on, channels with readEventsEnabled = false get a per-channel unread count tracked on-device. The user-level total unread count is unaffected.

Scope: best-effort, live-events only

By design this is a best-effort count of live events, not an authoritative unread count. These channels turn off server-side read tracking, so there is no read state to reconcile against — which means:

  • it counts messages that arrive while the app is running; it does not backfill messages received while the app was closed,
  • it is per-device (two devices can disagree), and
  • it resets on a fresh install (no prior local state to derive from).

This matches the intended behavior of the original feature, confirmed with the iOS team (author of stream-chat-swift#4102). Making it accurate across restarts/offline would require server-tracked reads, which is exactly what read_events = false disables.

Implementation

  • Increment: for a locally tracked channel, an incoming live message creates the current user's read on the fly (the server never sends one) and increments it. Skip rules (own/muted/silent/shadowed/thread-reply/already-read) apply, plus a skip for messages already in the channel state so a sync-replayed event isn't counted twice.
  • Mark read: markRead on a locally tracked channel resets the count in state without a network request (markRead() returns a new MarkReadResult describing whether a remote request is still needed) and refreshes the channel-list rows. It advances lastRead just past the last message, mirroring the server's mark-read, so the message list doesn't render a spurious unread separator for the user's own next message.
  • Server-data guard: the in-memory read merge and the DB-layer merge both keep the locally tracked read; server payloads only contribute user info and delivered-receipt fields. updateLastMessage also leaves the local read untouched, so the on-device count is the single writer (it would otherwise increment the persisted read with weaker guards).
  • Persistence: local reads are written to Room through a new internal ChannelRepository.upsertChannelReads (default no-op) that bypasses the server-data merge, so the last-known count survives an app restart.
  • Both the new and legacy channel-logic paths are covered.

Demo app

The compose sample gets a "Local unread count" toggle (advanced login options). When enabled it also includes livestream channels in the channel list so the feature is testable.

Testing

  • spotlessCheck, detekt, apiCheck (only additive: the new ChatClientConfig/StateRegistry trailing flag; source-compatible), and the full client testDebugUnitTest suite pass.
  • Unit tests cover the increment, local mark-read, the server-data guard (including tying-event-date and cold-start cases), and the sync-replay skip, on both channel-logic paths.

Manual testing:

  1. Enable Local unread count in the login advanced options and log in as a user who's a member of the livestream channel, e.g. Leia, Padmé and any other member of "Local Unread Test".
  2. With the app foregrounded and that channel closed, send a few messages from another user.
  3. The channel row's unread badge climbs by the number of messages received.
  4. Open the channel: the badge clears with no /read request.

Best-effort by design (see Scope): messages arriving while the app is backgrounded/killed, offline, or before a fresh install are not counted.

Summary by CodeRabbit

  • New Features

    • Added an optional local unread-count setting for channels without server read events; it is disabled by default.
    • Local unread counts persist across offline updates and remain consistent with channel state.
    • Added sample-app controls and localized descriptions for enabling the feature.
  • Bug Fixes

    • Improved read-state merging, duplicate message handling, and channel-list refresh behavior.
    • Read actions now correctly distinguish remote, local, and unnecessary operations.

@gpunto gpunto added the pr:new-feature New feature label Jul 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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, or the PR is bot-authored.
  • An issue is linked (Linear ticket or GitHub issue), or the PR is bot-authored.

🎉 Great job! This PR is ready for review.

@gpunto

gpunto commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.96 MB 5.96 MB 0.00 MB 🟢
stream-chat-android-ui-components 11.25 MB 11.25 MB 0.01 MB 🟢
stream-chat-android-compose 12.73 MB 12.74 MB 0.01 MB 🟢

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

ChatClientConfig adds optional local unread-count tracking. Channel state now distinguishes remote, local, and unnecessary read operations, preserves local read data, and persists updates. The Compose sample exposes the setting and adjusts initialization and channel queries.

Changes

Local unread-count support

Layer / File(s) Summary
Configuration and state wiring
stream-chat-android-client/api/..., stream-chat-android-client/src/main/java/.../api/..., stream-chat-android-client/src/main/java/.../state/..., stream-chat-android-client/src/main/java/.../factory/...
The isLocalUnreadCountEnabled flag flows from ChatClientConfig to StateRegistry, channel states, and EventHandlerSequential.
Read-state outcomes and channel logic
stream-chat-android-client/src/main/java/.../logic/channel/..., stream-chat-android-client/src/main/java/.../state/channel/..., stream-chat-android-client/src/main/java/.../listener/..., stream-chat-android-client/src/main/java/.../querychannels/...
MarkReadResult replaces Boolean read results. Current and legacy channel states support local read updates, unread increments, duplicate prevention, local-read merging, and configuration-before-read ordering.
Read persistence and event integration
stream-chat-android-client/src/main/java/.../offline/repository/..., stream-chat-android-client/src/main/java/.../persistance/repository/..., stream-chat-android-client/src/main/java/.../state/event/...
Channel repositories merge and persist reads with cache and database synchronization. Event handling persists locally tracked reads for eligible new-message channels.
Validation and Compose sample controls
stream-chat-android-client/src/test/..., stream-chat-android-compose-sample/src/main/..., stream-chat-android-compose-sample/src/main/res/...
Tests cover read outcomes, merge behavior, persistence, event ordering, and local unread counts. The sample stores the flag, exposes it in login settings, reinitializes the client when it changes, and applies matching channel filters.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: andremion, velikovpetar, aleksandar-apostolov

Poem

A rabbit counts each message bright,
Local reads persist through the night.
Remote and local results align,
Settings keep the sample in line.
Hop, hop—unread counts shine! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the primary change: opt-in local unread counts for channels with read events disabled.
Description check ✅ Passed The description covers the goal, scope, implementation, demo behavior, automated tests, and manual testing; omitted UI media and checklist items are non-critical.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/local-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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateImpl.kt (1)

1188-1213: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Mark-read outcomes are not in parity between the two channel-state implementations. The same mark-read decision table is implemented twice, and the two copies disagree on the empty-message case and on the unreadMessages check. A channel behaves differently depending on which state implementation is active.

  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateImpl.kt#L1188-L1213: add || currentUserRead.unreadMessages > 0 to the final condition, and align the empty-message-list outcome with the legacy implementation.
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateLegacyImpl.kt#L595-L624: keep the unreadMessages check, and align the empty-message-list outcome with the chosen behavior. Add a test for each branch in both implementations.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateImpl.kt`
around lines 1188 - 1213, Align mark-read decision logic across
ChannelStateImpl.markRead and ChannelStateLegacyImpl.markRead: include
currentUserRead.unreadMessages > 0 in the final condition, and make both
empty-message branches return the same chosen outcome. Add coverage for each
decision branch in both implementations. Update ChannelStateImpl.kt lines
1188-1213 and ChannelStateLegacyImpl.kt lines 595-624; tests should cover both
implementations and all affected branches.
🧹 Nitpick comments (3)
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/logic/channel/internal/ChannelLogicImpl.kt (1)

250-265: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Local-read persistence is implemented twice. Both channel-logic implementations read the current reads, skip empty lists, and launch upsertChannelReads on MarkReadResult.HandledLocally. Neither handles a persistence failure, so a failed upsert silently loses the local reset.

  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/logic/channel/internal/ChannelLogicImpl.kt#L250-L265: extract the persistence rule into one shared helper and log a failure from the launched coroutine.
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/logic/channel/internal/legacy/ChannelLogicLegacyImpl.kt#L203-L215: call the shared helper instead of repeating the block inline.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/logic/channel/internal/ChannelLogicImpl.kt`
around lines 250 - 265, Local-read persistence is duplicated and silently
ignores upsert failures. In ChannelLogicImpl.kt lines 250-265, extract the
reads-empty check and coroutine upsert into a shared helper that logs failures
from the launched coroutine; in ChannelLogicLegacyImpl.kt lines 203-215, replace
the inline persistence block with a call to that helper while preserving the
HandledLocally behavior.
stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt (1)

109-140: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated channel-list filter/sort construction into a shared helper.

Both activities build the same ChannelListViewModelFactory with identical KDoc, the same isLocalUnreadCountEnabled branch, and the same Filters/QuerySortByField configuration. Any future change to this filter (for example, adding another channel type) must be made in both places, and the two copies can drift.

  • stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt#L109-L140: replace this block with a call to a shared helper function that takes chatClient and settings and returns the built ChannelListViewModelFactory.
  • stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/chats/ChatsActivity.kt#L137-L168: replace this block with the same shared helper call.
♻️ Suggested shared helper
fun buildSampleChannelListViewModelFactory(
    chatClient: ChatClient,
    settings: CustomSettings,
): ChannelListViewModelFactory {
    val currentUserId = chatClient.getCurrentUser()?.id ?: ""
    return if (settings.isLocalUnreadCountEnabled) {
        ChannelListViewModelFactory(
            chatClient = chatClient,
            querySort = QuerySortByField<Channel>().desc("pinned_at").desc("last_updated"),
            filters = Filters.and(
                Filters.`in`("type", listOf("messaging", "livestream")),
                Filters.`in`("members", listOf(currentUserId)),
                Filters.or(Filters.notExists("draft"), Filters.eq("draft", false)),
            ),
            chatEventHandlerFactory = CustomChatEventHandlerFactory(),
        )
    } else {
        ChannelListViewModelFactory(
            chatClient = chatClient,
            predefinedFilterName = "android_sample_filter",
            filterValues = mapOf(
                "channel_type" to "messaging",
                "user_id" to currentUserId,
            ),
            chatEventHandlerFactory = CustomChatEventHandlerFactory(),
        )
    }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt`
around lines 109 - 140, Extract the duplicated channel-list factory construction
into a shared buildSampleChannelListViewModelFactory helper accepting ChatClient
and CustomSettings, preserving the existing local-unread filter/sort and
predefined-filter branches. In
stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt
lines 109-140 and
stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/chats/ChatsActivity.kt
lines 137-168, replace each duplicated block with a call to this helper using
the activity’s chatClient and settings.
stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/event/handler/internal/EventHandlerSequentialTest.kt (1)

424-473: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the NotificationMessageNewEvent branch.

These two tests cover persistLocallyTrackedReads only through randomNewMessageEvent. The production when block in persistLocallyTrackedReads also matches NotificationMessageNewEvent. Add a test using randomNotificationMessageNewEvent to confirm that branch also triggers repos.upsertChannelReads.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/event/handler/internal/EventHandlerSequentialTest.kt`
around lines 424 - 473, Add a test alongside the existing local unread tracking
tests that invokes persistLocallyTrackedReads through
randomNotificationMessageNewEvent, with local unread tracking enabled and
locally tracked reads configured, then verify repos.upsertChannelReads receives
the channel ID and reads. Keep the setup and assertions consistent with the
existing randomNewMessageEvent coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channel/internal/DatabaseChannelRepository.kt`:
- Around line 71-98: Move all fallback DB/user resolution out of
cacheMutex.withLock: preload the required channel rows and resolved reads before
the lock in preserveLocallyTrackedReads and the upsertChannelReads flow,
including selectChannel. Pass the preloaded in-memory data into the locked merge
so cacheMutex only protects synchronous cache updates; preserve existing merge
behavior and avoid any channelDao.select or getUser calls while the lock is
held.

In
`@stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/listener/internal/ChannelMarkReadListenerState.kt`:
- Around line 45-55: Update the HandledLocally branch in
ChannelMarkReadListenerState to return Result.Success(Unit) after refreshing
active query-channel state. Preserve the existing refreshChannelState call and
ensure callers treat a locally completed mark-read as successful rather than
returning Error.GenericError.

---

Outside diff comments:
In
`@stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateImpl.kt`:
- Around line 1188-1213: Align mark-read decision logic across
ChannelStateImpl.markRead and ChannelStateLegacyImpl.markRead: include
currentUserRead.unreadMessages > 0 in the final condition, and make both
empty-message branches return the same chosen outcome. Add coverage for each
decision branch in both implementations. Update ChannelStateImpl.kt lines
1188-1213 and ChannelStateLegacyImpl.kt lines 595-624; tests should cover both
implementations and all affected branches.

---

Nitpick comments:
In
`@stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/logic/channel/internal/ChannelLogicImpl.kt`:
- Around line 250-265: Local-read persistence is duplicated and silently ignores
upsert failures. In ChannelLogicImpl.kt lines 250-265, extract the reads-empty
check and coroutine upsert into a shared helper that logs failures from the
launched coroutine; in ChannelLogicLegacyImpl.kt lines 203-215, replace the
inline persistence block with a call to that helper while preserving the
HandledLocally behavior.

In
`@stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/event/handler/internal/EventHandlerSequentialTest.kt`:
- Around line 424-473: Add a test alongside the existing local unread tracking
tests that invokes persistLocallyTrackedReads through
randomNotificationMessageNewEvent, with local unread tracking enabled and
locally tracked reads configured, then verify repos.upsertChannelReads receives
the channel ID and reads. Keep the setup and assertions consistent with the
existing randomNewMessageEvent coverage.

In
`@stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt`:
- Around line 109-140: Extract the duplicated channel-list factory construction
into a shared buildSampleChannelListViewModelFactory helper accepting ChatClient
and CustomSettings, preserving the existing local-unread filter/sort and
predefined-filter branches. In
stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt
lines 109-140 and
stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/chats/ChatsActivity.kt
lines 137-168, replace each duplicated block with a call to this helper using
the activity’s chatClient and settings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 742011a2-2f22-40a3-b7b8-24b4e4b64927

📥 Commits

Reviewing files that changed from the base of the PR and between f0a5137 and 31957ed.

📒 Files selected for processing (37)
  • stream-chat-android-client/api/stream-chat-android-client.api
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api/ChatClientConfig.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api/state/StateRegistry.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channel/internal/DatabaseChannelRepository.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/factory/internal/DatabaseRepositoryFactory.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/event/handler/internal/EventHandlerSequential.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/factory/StreamStatePluginFactory.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/listener/internal/ChannelMarkReadListenerState.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/logic/channel/internal/ChannelLogic.kt
  • 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/logic/channel/internal/legacy/ChannelEventHandlerLegacyImpl.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/logic/channel/internal/legacy/ChannelLogicLegacyImpl.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/logic/channel/internal/legacy/ChannelStateLogic.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/logic/querychannels/internal/QueryChannelsStateLogic.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/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateLegacyImpl.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/MarkReadResult.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/persistance/repository/ChannelRepository.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/domain/channel/internal/ChannelRepositoryImplTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/event/TotalUnreadCountTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/event/handler/internal/EventHandlerSequentialTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/event/handler/internal/EventHandlerSequentialUserMessagesDeletedTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/plugin/listener/internal/ChannelMarkReadListenerStateTest.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/logic/channel/internal/legacy/ChannelStateLogicTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/plugin/logic/querychannels/internal/QueryChannelsStateLogicTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateImplLocalUnreadCountTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateImplReadReceiptsTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateLegacyImplLocalUnreadCountTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateLegacyImplTest.kt
  • stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ChatHelper.kt
  • stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/data/CustomSettings.kt
  • stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt
  • stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/chats/ChatsActivity.kt
  • stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/login/CustomLoginActivity.kt
  • stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/login/UserLoginActivity.kt
  • stream-chat-android-compose-sample/src/main/res/values/strings.xml

@gpunto
gpunto force-pushed the feat/local-unread-count branch from a5e0cc0 to e77e987 Compare July 31, 2026 13:26
…count

# Conflicts:
#	stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/logic/channel/internal/legacy/ChannelStateLogic.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/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateLegacyImpl.kt
#	stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateLegacyImplTest.kt
@gpunto

gpunto commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@sonarqubecloud

sonarqubecloud Bot commented Aug 3, 2026

Copy link
Copy Markdown

@gpunto
gpunto force-pushed the feat/local-unread-count branch from 8051d03 to a0be421 Compare August 4, 2026 14:51
@gpunto

gpunto commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/extensions/internal/Channel.kt (1)

86-103: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Use isLocalUnreadCountEnabled for the locally tracked read guard.

readEventsEnabled = false is not the opt-in flag, so this branch must also check clientConfig.isLocalUnreadCountEnabled before skipping the current-user read update. Otherwise non-opted-in channels with server-side read events disabled still get their own user read copied in updateLastMessage, bypassing the per-channel legacy/local-track guard.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@stream-chat-android-client/src/main/java/io/getstream/chat/android/client/extensions/internal/Channel.kt`
around lines 86 - 103, Update the current-user read branch in Channel’s newReads
mapping to skip the update only when both readEventsEnabled is false and
clientConfig.isLocalUnreadCountEnabled is true. Use the existing
isLocalUnreadCountEnabled configuration symbol, preserving the normal read.copy
path for channels not opted into local unread tracking.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@stream-chat-android-client/src/main/java/io/getstream/chat/android/client/extensions/internal/Channel.kt`:
- Around line 86-103: Update the current-user read branch in Channel’s newReads
mapping to skip the update only when both readEventsEnabled is false and
clientConfig.isLocalUnreadCountEnabled is true. Use the existing
isLocalUnreadCountEnabled configuration symbol, preserving the normal read.copy
path for channels not opted into local unread tracking.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e507346b-5545-461b-8f18-6cf66f739589

📥 Commits

Reviewing files that changed from the base of the PR and between 9ec3580 and 40b6b2d.

📒 Files selected for processing (3)
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/extensions/internal/Channel.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/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateLegacyImpl.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateLegacyImpl.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/state/channel/internal/ChannelStateImpl.kt

@gpunto
gpunto marked this pull request as ready for review August 5, 2026 08:24
@gpunto
gpunto requested a review from a team as a code owner August 5, 2026 08:24
@gpunto
gpunto force-pushed the feat/local-unread-count branch from 22d8ede to c5e87fc Compare August 7, 2026 12:15
@sonarqubecloud

sonarqubecloud Bot commented Aug 7, 2026

Copy link
Copy Markdown

@gpunto
gpunto added this pull request to the merge queue Aug 7, 2026
Merged via the queue into develop with commit 85492aa Aug 7, 2026
19 checks passed
@gpunto
gpunto deleted the feat/local-unread-count branch August 7, 2026 14:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:new-feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants