Conversation
… updated web-socket event is received
📝 WalkthroughWalkthroughUpdates the channel list query handling to link channels that pass the current filter after receiving a channel update event. When a ChannelUpdatedEvent occurs, the system now chains an unlink operation followed by a link operation via completion callback. Tests verify this behavior, and the demo app adds a toggle action for premium tag management. Changes
Sequence Diagram(s)sequenceDiagram
participant WebSocket as WebSocket Event
participant Linker as ChannelListLinker
participant Query as ChannelListQuery
participant Channel as ChatChannel
WebSocket->>Linker: ChannelUpdatedEvent received
Linker->>Linker: unlinkChannelIfNeeded(channel)
rect rgba(100, 150, 200, 0.5)
Note over Linker,Query: Unlink operation
Linker->>Query: Remove channel from query
Query-->>Linker: Unlink complete (completion)
end
Linker->>Linker: linkChannelIfNeeded(channel)<br/>(via completion callback)
rect rgba(100, 200, 150, 0.5)
Note over Linker,Query: Link operation if filter matches
Linker->>Channel: Check if passes filter
Channel-->>Linker: Filter result
alt Filter matches
Linker->>Query: Add channel to query
Query-->>Linker: Link complete
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
Generated by 🚫 Danger |
Public Interface🚀 No changes affecting the public interface. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift (1)
571-573: Add an explicit no-unlink assertion for this matching-filter path.You already validate linking on Line 571-Line 572; adding
XCTAssertEqual(env.channelListUpdater?.unlink_callCount, 0)makes this regression test stricter.Based on learnings: Prioritize backwards compatibility, API stability, and high test coverage when changing code in the Stream iOS Chat SDK.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift` around lines 571 - 573, Add an explicit no-unlink assertion to the matching-filter test by asserting env.channelListUpdater?.unlink_callCount is 0 immediately after the existing link and watch assertions; specifically, in ChannelListController_Tests where you call XCTAssertEqual(env.channelListUpdater?.link_callCount, 1) and XCTAssertEqual(env.channelWatcherHandler?.attemptToWatch_callCount, 1), add XCTAssertEqual(env.channelListUpdater?.unlink_callCount, 0) to ensure the matching-filter path does not trigger an unlink.Sources/StreamChat/Workers/ChannelListLinker.swift (1)
59-63: Update the header docs to match the newChannelUpdatedEventbehavior.Line 12-Line 13 still says updated channels are only removed, but Line 61 now links matching channels after update handling.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/StreamChat/Workers/ChannelListLinker.swift` around lines 59 - 63, Update the header documentation for ChannelUpdatedEvent in ChannelListLinker.swift to reflect the new behavior: it no longer only removes updated channels but may also re-link them after handling; specifically change the lines that state "updated channels are only removed" (around the ChannelUpdatedEvent description) to describe that the callback now calls unlinkChannelIfNeeded(event.channel) and then linkChannelIfNeeded(event.channel), so updated channels can be removed and conditionally re-linked based on matching logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@DemoApp/StreamChat/Components/DemoChatChannelListRouter.swift`:
- Around line 508-510: The toggle currently overwrites all filterTags via
channelController.partialChannelUpdate(filterTags: ["premium"/"non-premium"])
which drops unrelated tags; instead read the current tags from
channelController.channel?.filterTags, create a new set/array that preserves all
existing tags but adds "premium" when toggling on or removes "premium" when
toggling off (do not replace other tags), then call
channelController.partialChannelUpdate(filterTags: newTags) { error in ... } so
only the premium-related tag is changed while other filterTags remain intact.
In `@Sources/StreamChat/Workers/ChannelListLinker.swift`:
- Around line 122-134: The spy ChannelListUpdater_Spy currently increments
unlink_callCount but does not invoke the unlink completion, causing callers of
ChannelListUpdater.unlink(channel:with:completion:) (used by
ChannelListLinker.unlinkChannelIfNeeded) to never continue the post-unlink flow;
update the spy's unlink(channel:with:completion:) implementation to call the
provided completion (e.g., completion?(nil)) or store the completion for
test-controlled invocation so tests observe the same behavior as the production
unlink implementation and allow the chained completion in
ChannelListLinker.unlinkChannelIfNeeded to run.
---
Nitpick comments:
In `@Sources/StreamChat/Workers/ChannelListLinker.swift`:
- Around line 59-63: Update the header documentation for ChannelUpdatedEvent in
ChannelListLinker.swift to reflect the new behavior: it no longer only removes
updated channels but may also re-link them after handling; specifically change
the lines that state "updated channels are only removed" (around the
ChannelUpdatedEvent description) to describe that the callback now calls
unlinkChannelIfNeeded(event.channel) and then
linkChannelIfNeeded(event.channel), so updated channels can be removed and
conditionally re-linked based on matching logic.
In
`@Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift`:
- Around line 571-573: Add an explicit no-unlink assertion to the
matching-filter test by asserting env.channelListUpdater?.unlink_callCount is 0
immediately after the existing link and watch assertions; specifically, in
ChannelListController_Tests where you call
XCTAssertEqual(env.channelListUpdater?.link_callCount, 1) and
XCTAssertEqual(env.channelWatcherHandler?.attemptToWatch_callCount, 1), add
XCTAssertEqual(env.channelListUpdater?.unlink_callCount, 0) to ensure the
matching-filter path does not trigger an unlink.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
CHANGELOG.mdDemoApp/StreamChat/Components/DemoChatChannelListRouter.swiftSources/StreamChat/Workers/ChannelListLinker.swiftTests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
| .init(title: "Toggle Premium Tag", isEnabled: canUpdateChannel, handler: { [unowned self] _ in | ||
| let hasPremium = channelController.channel?.filterTags.contains("premium") ?? false | ||
| channelController.partialChannelUpdate(filterTags: hasPremium ? ["non-premium"] : ["premium"]) { error in |
There was a problem hiding this comment.
Avoid overwriting unrelated filterTags while toggling premium state.
On Line 510, replacing tags with ["premium"] / ["non-premium"] drops any existing non-premium tags. Toggle only the premium-related tags and preserve the rest.
💡 Suggested fix
- let hasPremium = channelController.channel?.filterTags.contains("premium") ?? false
- channelController.partialChannelUpdate(filterTags: hasPremium ? ["non-premium"] : ["premium"]) { error in
+ var updatedTags = Set(channelController.channel?.filterTags ?? [])
+ if updatedTags.contains("premium") {
+ updatedTags.remove("premium")
+ updatedTags.insert("non-premium")
+ } else {
+ updatedTags.remove("non-premium")
+ updatedTags.insert("premium")
+ }
+ channelController.partialChannelUpdate(filterTags: updatedTags) { error in🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@DemoApp/StreamChat/Components/DemoChatChannelListRouter.swift` around lines
508 - 510, The toggle currently overwrites all filterTags via
channelController.partialChannelUpdate(filterTags: ["premium"/"non-premium"])
which drops unrelated tags; instead read the current tags from
channelController.channel?.filterTags, create a new set/array that preserves all
existing tags but adds "premium" when toggling on or removes "premium" when
toggling off (do not replace other tags), then call
channelController.partialChannelUpdate(filterTags: newTags) { error in ... } so
only the premium-related tag is changed while other filterTags remain intact.
| private func unlinkChannelIfNeeded(_ channel: ChatChannel, completion: (() -> Void)? = nil) { | ||
| guard !shouldChannelBelongToCurrentQuery(channel) else { | ||
| completion?() | ||
| return | ||
| } | ||
| isInChannelList(channel) { [worker, query] exists, _ in | ||
| guard exists else { return } | ||
| worker.unlink(channel: channel, with: query) | ||
| guard exists else { | ||
| completion?() | ||
| return | ||
| } | ||
| worker.unlink(channel: channel, with: query) { _ in | ||
| completion?() | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== 1. Locate ChannelListUpdater_Spy.swift ==="
find . -name "ChannelListUpdater_Spy.swift" -type f 2>/dev/null | head -5
echo
echo "=== 2. Contents of ChannelListUpdater_Spy.swift ==="
fd "ChannelListUpdater_Spy.swift" --type f -x cat -n {}
echo
echo "=== 3. Search for unlink method in spy implementations ==="
rg -n "func unlink" --type swift -B2 -A10
echo
echo "=== 4. Check ChannelListUpdater production implementation ==="
fd "ChannelListUpdater.swift" --type f --not-path "*/Test*" --not-path "*Spy*" -x cat -n {}Repository: GetStream/stream-chat-swift
Length of output: 8394
Spy unlink method must invoke completion callback to align with production contract.
The spy implementation (lines 87-93 in ChannelListUpdater_Spy.swift) only increments unlink_callCount without invoking the completion callback, while the production implementation (lines 159-168 in ChannelListUpdater.swift) invokes completion?(error) on line 166. The new sequencing in ChannelListLinker.unlinkChannelIfNeeded at lines 132-134 chains a follow-up completion handler that depends on this callback being invoked. Without the spy invoking it, tests will skip the post-unlink completion flow and diverge from production behavior.
Update the spy to invoke completion?(nil) or capture it for manual control, consistent with test expectations.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Sources/StreamChat/Workers/ChannelListLinker.swift` around lines 122 - 134,
The spy ChannelListUpdater_Spy currently increments unlink_callCount but does
not invoke the unlink completion, causing callers of
ChannelListUpdater.unlink(channel:with:completion:) (used by
ChannelListLinker.unlinkChannelIfNeeded) to never continue the post-unlink flow;
update the spy's unlink(channel:with:completion:) implementation to call the
provided completion (e.g., completion?(nil)) or store the completion for
test-controlled invocation so tests observe the same behavior as the production
unlink implementation and allow the chained completion in
ChannelListLinker.unlinkChannelIfNeeded to run.
SDK Performance
|
SDK Size
|
StreamChat XCSize
|
|



🔗 Issue Links
Fixes: IOS-1449
🎯 Goal
Fix adding and removing channels from channel list query when channel updated web-socket event is received
📝 Summary
🛠 Implementation
🎨 Showcase
output_after.mp4
🧪 Manual Testing Notes
☑️ Contributor Checklist
docs-contentrepoSummary by CodeRabbit
Release Notes
Bug Fixes
New Features
Tests