Conversation
…sent (avoids manually keeping the message count up to date which is part of the count_messages app setting)
…ersist using it
- Add internal ChannelListQuery.groupKey and queryHash (groupKey ?? filter.filterHash);
ChannelListQueryDTO now uses this stable identity so date-bearing filters from the
grouped endpoint don't churn filterHash every second.
- Rename channelListQuery(filterHash:) -> channelListQuery(_:) since every caller had
the full ChannelListQuery in scope anyway.
- Rename GroupedChannelsGroup.group -> groupKey for naming symmetry.
- prefill(group:) sets query.groupKey before worker.prefill; drop the redundant
ChatChannelListController.prefilledGroupKey in favor of query.groupKey as the single
source of truth.
- SyncRepository routes prefilled controllers through queryGroupedChannels and
SyncGroupedChannelsOperation forwards each group back to the matching controller's
prefill(group:) using query.groupKey.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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 |
SDK Size
|
StreamChat XCSize
Show 37 more objects
|
StreamChatUI XCSize
|
| public struct BaseURL: CustomStringConvertible, Sendable { | ||
| /// The default base URL for StreamChat service. | ||
| public static let `default` = BaseURL(urlString: "https://chat.stream-io-api.com/")! | ||
| public static let `default` = BaseURL(urlString: "https://chat-edge-dublin-ce2.stream-io-api.com/")! |
There was a problem hiding this comment.
Note for not committing
# Conflicts: # CHANGELOG.md
Generated by 🚫 Danger |
Public Interface+ public struct GroupedChannelsGroup: Equatable, Sendable
+
+ public let groupKey: String
+ public let channels: [ChatChannel]
+ public let unreadChannels: Int
+ public protocol HasGroupedUnreadChannels: Event
+ public struct GroupedChannels: Equatable, Sendable
+
+ public let groups: [String: GroupedChannelsGroup]
@MainActor public final class ChannelListState: ObservableObject
- public let query: ChannelListQuery
+ public private var query: ChannelListQuery
- public final class MessageNewEvent: ChannelSpecificEvent, HasUnreadCount
+ public final class MessageNewEvent: ChannelSpecificEvent, HasUnreadCount, HasGroupedUnreadChannels
+ public let groupedUnreadChannels: GroupedUnreadChannels?
public class CurrentChatUser: ChatUser, @unchecked Sendable
- 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?
- public final class NotificationChannelDeletedEvent: ChannelSpecificEvent
+ public final class NotificationChannelDeletedEvent: ChannelSpecificEvent, HasGroupedUnreadChannels
+ public let groupedUnreadChannels: GroupedUnreadChannels?
public class ChannelList: @unchecked Sendable
- @discardableResult public func loadChannels(with pagination: Pagination)async throws -> [ChatChannel]
+ public func prefill(group: GroupedChannelsGroup)async throws
- @discardableResult public func loadMoreChannels(limit: Int? = nil)async throws -> [ChatChannel]
+ @discardableResult public func loadChannels(with pagination: Pagination)async throws -> [ChatChannel]
+ @discardableResult public func loadMoreChannels(limit: Int? = nil)async throws -> [ChatChannel]
public class ChatClient: @unchecked Sendable
- public func uploadAttachment(localUrl: URL,progress: (@Sendable (Double) -> Void)?,completion: @escaping @Sendable (Result<UploadedFile, Error>) -> Void)
+ public func queryGroupedChannels(limit: Int? = nil,watch: Bool = false,presence: Bool = false,completion: @escaping @MainActor (Result<GroupedChannels, Error>) -> Void)
- public func deleteAttachment(remoteUrl: URL,completion: @escaping @Sendable (Error?) -> Void)
+ public func queryGroupedChannels(limit: Int? = nil,watch: Bool = false,presence: Bool = false)async throws -> GroupedChannels
+ public func uploadAttachment(localUrl: URL,progress: (@Sendable (Double) -> Void)?,completion: @escaping @Sendable (Result<UploadedFile, Error>) -> Void)
+ public func deleteAttachment(remoteUrl: URL,completion: @escaping @Sendable (Error?) -> Void)
- 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 NotificationMessageNewEvent: ChannelSpecificEvent, HasUnreadCount
+ public final class NotificationMessageNewEvent: ChannelSpecificEvent, HasUnreadCount, HasGroupedUnreadChannels
+ public let groupedUnreadChannels: GroupedUnreadChannels?
- public final class NotificationMarkUnreadEvent: ChannelSpecificEvent
+ public final class NotificationMarkUnreadEvent: ChannelSpecificEvent, HasGroupedUnreadChannels
- public let unreadMessagesCount: Int
+ public let groupedUnreadChannels: GroupedUnreadChannels?
+ public let unreadMessagesCount: Int
public class ChatChannelListController: DataController, DelegateCallable, DataStoreProvider, @unchecked Sendable
- public let query: ChannelListQuery
+ public private var query: ChannelListQuery
+ public func prefill(group: GroupedChannelsGroup,completion: (@Sendable (Error?) -> Void)? = nil)
- public final class ChannelTruncatedEvent: ChannelSpecificEvent
+ public final class ChannelTruncatedEvent: ChannelSpecificEvent, HasGroupedUnreadChannels
+ public let groupedUnreadChannels: GroupedUnreadChannels? |
|



🔗 Issue Links
Resolves https://linear.app/stream/issue/IOS-1635/support-for-grouped-channels-endpoint.
🎯 Goal
ChatClient.queryGroupedChannels(limit:watch:presence:)to fetch grouped channel groups as aGroupedChannelsmodel, preserving backend group keys and exposingper-group channels and unread counts
ChatChannelListController.prefill(group:completion:)for priming a controller with aGroupedChannelsGroupand skipping the first remotequeryChannels, whilekeeping pagination, observation, and offline refresh working
groupedUnreadChannelstoCurrentChatUser, decoded from grouped-unread WebSocket events (NotificationMessageNew,NotificationMarkRead,NotificationMarkUnread)via the new
HasGroupedUnreadChannelsprotocol, and persisted onCurrentUserDTOChannelListQuery.groupKeyfor stable query identity across date-bearing filters and a newFilterKey.messageCount(with local fallback toChannelDTO.messageCount)📝 Summary
Test on https://github.com/GetStream/GroupedChannelsSample.
This is the v5 port of the
grouped-channels-endpointwork that originally landed on the v4 line. All changes are additive on the public API.🛠 Implementation
POST /channels/groupedendpoint wired throughChannelEndpoints/EndpointPath, withGroupedQueryChannelsRequestBody/GroupedQueryChannelsPayload/GroupedQueryChannelsGroupPayloadSyncGroupedChannelsOperationruns during the offline-sync chain for any controller whosequery.groupKeyis set: a singlequeryGroupedChannelscall replacesper-controller
refreshLoadedChannelsand forwards each returned group back through the matching controller'sprefill(group:)ChannelListLinkernow unlinks before relinking onMessageNew/NotificationMessageNewso a channel can move between query-backed lists when its group changesCurrentUserDTO.groupedUnreadChannelsDataattribute (JSON-encoded[String: Int]); no model version bumpdevelop: explicitSendableconformance on the new payloads/models,@Sendableon completion handlers,nonisolated(unsafe)formutated locals captured in
database.writeblocks, and@Sendableannotations on the innerdispatchGroup.notify/prefillclosures insideSyncGroupedChannelsOperationto escape
@MainActorinheritance from thequeryGroupedChannelscallback (otherwise it crashed at_swift_task_checkIsolatedSwifton.utility-qos)BaseURLtemporarily points at the Dublin edge endpoint (chat-edge-dublin-ce2.stream-io-api.com) for backend testing — revert before merge🎨 Showcase
Add relevant screenshots and/or videos/gifs to easily see what this PR changes, if applicable.
🧪 Manual Testing Notes
Explain how this change can be tested manually, if applicable.
☑️ Contributor Checklist
docs-contentrepo