Skip to content

Grouped channels endpoint#4071

Draft
martinmitrevski wants to merge 12 commits intov4from
grouped-channels-endpoint
Draft

Grouped channels endpoint#4071
martinmitrevski wants to merge 12 commits intov4from
grouped-channels-endpoint

Conversation

@martinmitrevski
Copy link
Copy Markdown
Contributor

🔗 Issue Links

Resolves https://linear.app/stream/issue/IOS-1635/support-for-grouped-channels-endpoint.

🎯 Goal

  • Add ChatChannelListController.prefill(channels:completion:) for priming controller-local channel data before the first synchronize call while preserving normal pagination, observation, and offline refresh behavior
  • Add ChatClient.groupedQueryChannels(limit:watch:presence:) to fetch grouped channel groups as GroupedChannels, preserving backend group keys and exposing normalized per-group channels and unread counts for integrators
  • Add optional groupedUnreadChannels data to grouped unread websocket events and persist it on CurrentChatUser for integrators

📝 Summary

Test on https://github.com/GetStream/GroupedChannelsSample.

🛠 Implementation

Provide a detailed description of the implementation and explain your decisions if you find them relevant.

🎨 Showcase

Add relevant screenshots and/or videos/gifs to easily see what this PR changes, if applicable.

Before After
img img

🧪 Manual Testing Notes

Explain how this change can be tested manually, if applicable.

☑️ Contributor Checklist

  • I have signed the Stream CLA (required)
  • This change should be manually QAed
  • Changelog is updated with client-facing changes
  • Changelog is updated with new localization keys
  • New code is covered by unit tests
  • Documentation has been updated in the docs-content repo

@martinmitrevski martinmitrevski requested a review from a team as a code owner April 20, 2026 11:10
@martinmitrevski martinmitrevski marked this pull request as draft April 20, 2026 11:10
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 20, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3fe20afc-b06e-4df7-97e4-30680e3d42ca

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch grouped-channels-endpoint

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.

@github-actions
Copy link
Copy Markdown

1 Message
📖 Skipping Danger since the Pull Request is classed as Draft/Work In Progress

Generated by 🚫 Danger

@Stream-SDK-Bot
Copy link
Copy Markdown
Collaborator

SDK Performance

target metric benchmark branch performance status
MessageList Hitches total duration 10 ms 6.68 ms 33.2% 🔼 🟢
Duration 2.6 s 2.55 s 1.92% 🔼 🟢
Hitch time ratio 4 ms per s 2.63 ms per s 34.25% 🔼 🟢
Frame rate 75 fps 78.86 fps 5.15% 🔼 🟢
Number of hitches 1 0.8 20.0% 🔼 🟢

@github-actions
Copy link
Copy Markdown

Public Interface

+ public struct GroupedChannelsGroup: Equatable  
+ 
+   public let channels: [ChatChannel]
+   public let unreadCount: Int
+   public let unreadChannels: Int
+   
+ 
+   public init(channels: [ChatChannel],unreadCount: Int,unreadChannels: Int)

+ public struct GroupedChannels: Equatable  
+ 
+   public let groups: [String: GroupedChannelsGroup]
+   
+ 
+   public init(groups: [String: GroupedChannelsGroup])

+ public protocol HasGroupedUnreadChannels: Event



- public final class MessageNewEvent: ChannelSpecificEvent, HasUnreadCount  
+ public final class MessageNewEvent: ChannelSpecificEvent, HasUnreadCount, HasGroupedUnreadChannels  
+   public let groupedUnreadChannels: GroupedUnreadChannels?

 public class ChatChannelListController: DataController, DelegateCallable, DataStoreProvider  
-   @available(*, deprecated, message: "Please use `markAllRead` available in `CurrentChatUserController`") public func markAllRead(completion: ((Error?) -> Void)? = nil)
+   public func prefill(channels: [ChatChannel],completion: ((Error?) -> Void)? = nil)
+   @available(*, deprecated, message: "Please use `markAllRead` available in `CurrentChatUserController`") public func markAllRead(completion: ((Error?) -> Void)? = nil)

- public final class NotificationMarkReadEvent: ChannelSpecificEvent, HasUnreadCount  
+ public final class NotificationMarkReadEvent: ChannelSpecificEvent, HasUnreadCount, HasGroupedUnreadChannels  
-   public let lastReadMessageId: MessageId?
+   public let groupedUnreadChannels: GroupedUnreadChannels?
-   public let createdAt: Date
+   public let lastReadMessageId: MessageId?
+   public let createdAt: Date

- public final class NotificationMarkUnreadEvent: ChannelSpecificEvent  
+ public final class NotificationMarkUnreadEvent: ChannelSpecificEvent, HasGroupedUnreadChannels  
-   public let unreadMessagesCount: Int
+   public let groupedUnreadChannels: GroupedUnreadChannels?
+   public let unreadMessagesCount: Int

- public final class NotificationMessageNewEvent: ChannelSpecificEvent, HasUnreadCount  
+ public final class NotificationMessageNewEvent: ChannelSpecificEvent, HasUnreadCount, HasGroupedUnreadChannels  
+   public let groupedUnreadChannels: GroupedUnreadChannels?

 public class ChatClient  
-   @available(*, deprecated, message: "Use the asynchronous version of `disconnect` for increased safety") public func disconnect()
+   public func groupedQueryChannels(limit: Int? = nil,watch: Bool = false,presence: Bool = false,completion: @escaping (Result<GroupedChannels, Error>) -> Void)
-   public func disconnect(completion: @escaping () -> Void)
+   public func groupedQueryChannels(limit: Int? = nil,watch: Bool = false,presence: Bool = false)async throws -> GroupedChannels
-   public func disconnect()async 
+   @available(*, deprecated, message: "Use the asynchronous version of `disconnect` for increased safety") public func disconnect()
-   @available(*, deprecated, message: "Use the asynchronous version of `logout` for increased safety") public func logout()
+   public func disconnect(completion: @escaping () -> Void)
-   public func logout(removeDevice: Bool = true,completion: @escaping () -> Void)
+   public func disconnect()async 
-   public func logout()async 
+   @available(*, deprecated, message: "Use the asynchronous version of `logout` for increased safety") public func logout()
-   public func subscribe(toEvent event: E.Type,handler: @escaping (E) -> Void)-> AnyCancellable
+   public func logout(removeDevice: Bool = true,completion: @escaping () -> Void)
-   public func subscribe(_ handler: @escaping (Event) -> Void)-> AnyCancellable
+   public func logout()async 
-   public func loadAppSettings(completion: ((Result<AppSettings, Error>) -> Void)? = nil)
+   public func subscribe(toEvent event: E.Type,handler: @escaping (E) -> Void)-> AnyCancellable
-   public func loadAppSettings()async throws -> AppSettings
+   public func subscribe(_ handler: @escaping (Event) -> Void)-> AnyCancellable
-   public func upload(_ attachment: StreamAttachment<Payload>,progress: ((Double) -> Void)?,completion: @escaping (Result<UploadedFile, Error>) -> Void)
+   public func loadAppSettings(completion: ((Result<AppSettings, Error>) -> Void)? = nil)
-   public func uploadAttachment(localUrl: URL,progress: ((Double) -> Void)?,completion: @escaping (Result<UploadedFile, Error>) -> Void)
+   public func loadAppSettings()async throws -> AppSettings
-   public func deleteAttachment(remoteUrl: URL,completion: @escaping (Error?) -> Void)
+   public func upload(_ attachment: StreamAttachment<Payload>,progress: ((Double) -> Void)?,completion: @escaping (Result<UploadedFile, Error>) -> Void)
+   public func uploadAttachment(localUrl: URL,progress: ((Double) -> Void)?,completion: @escaping (Result<UploadedFile, Error>) -> Void)
+   public func deleteAttachment(remoteUrl: URL,completion: @escaping (Error?) -> Void)

- public final class ChannelTruncatedEvent: ChannelSpecificEvent  
+ public final class ChannelTruncatedEvent: ChannelSpecificEvent, HasGroupedUnreadChannels  
+   public let groupedUnreadChannels: GroupedUnreadChannels?

- public final class NotificationChannelDeletedEvent: ChannelSpecificEvent  
+ public final class NotificationChannelDeletedEvent: ChannelSpecificEvent, HasGroupedUnreadChannels  
+   public let groupedUnreadChannels: GroupedUnreadChannels?

 public class CurrentChatUser: ChatUser  
-   public let isInvisible: Bool
+   public let groupedUnreadChannels: GroupedUnreadChannels?
-   public let privacySettings: UserPrivacySettings
+   public let isInvisible: Bool
-   public let pushPreference: PushPreference?
+   public let privacySettings: UserPrivacySettings
+   public let pushPreference: PushPreference?

@Stream-SDK-Bot
Copy link
Copy Markdown
Collaborator

SDK Size

title v4 branch diff status
StreamChat 8.6 MB 8.65 MB +58 KB 🟢
StreamChatUI 4.94 MB 4.94 MB 0 KB 🟢

@Stream-SDK-Bot
Copy link
Copy Markdown
Collaborator

StreamChat XCSize

Object Diff (bytes)
CDNClient.o +16484
ChannelListPayload.o +13577
ChatClient.o +7485
RequestEncoder.o +6994
ChannelListController.o +5314
Show 28 more objects
Object Diff (bytes)
ChannelController.o +5000
ChannelListUpdater.o +4230
EndpointPath.o +2107
PollVoteDTO.o +2080
ChannelListLinker.o +1804
CurrentUserDTO.o +1334
CurrentUserController.o -1300
ChatMessage.o +920
NotificationEvents.o +652
ChannelEvents.o +536
ChannelDTO.o +512
EventPayload.o +437
ErrorPayload.o +328
AttachmentDownloader.o +327
CurrentUserPayloads.o -324
MessagePayloads.o -304
MessageEvents.o +244
APIClient.o +194
CurrentUser.o +194
DatabaseSession.o +184
Event.o +162
CurrentUserUpdater.o -160
FlagMessagePayload.o -156
RawJSON.o +104
ThreadDTO.o +104
PollsPayloads.o -80
UserListController.o -48
UserController.o -48

@sonarqubecloud
Copy link
Copy Markdown

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.

2 participants