Skip to content

[v4] Support predefined filters in ChannelListQuery#4120

Merged
laevandus merged 4 commits into
v4from
v4-predefined-filters
Jun 3, 2026
Merged

[v4] Support predefined filters in ChannelListQuery#4120
laevandus merged 4 commits into
v4from
v4-predefined-filters

Conversation

@laevandus
Copy link
Copy Markdown
Contributor

🔗 Issue Links

https://linear.app/stream/issue/IOS-1706

🎯 Goal

Backport of #4113 to the v4 release line.

Let apps reference a server-side predefined channel-list filter by name and have the server resolve its filter/sort templates, instead of constructing the filter client-side.

📝 Summary

  • New ChannelListQuery(predefinedFilter:filterValues:sortValues:) initializer.
  • Server-resolved filter/sort returned on the predefined_filter response are decoded and re-applied locally for Core Data caching.
  • ChatChannelListController.query and ChannelListState.query become internal(set) so the SDK can swap to the resolved query after the first response.
  • Filter decoder now supports multi-key objects as implicit $and and the { key: value } short form as implicit $eq, matching the Stream filter JSON shape.

🛠 Implementation

  • ChannelListQuery+PredefinedFilter.swift decodes persisted filter/sort JSON and re-attaches Core Data wiring (keyPathString, valueMapper, predicateMapper) via ChannelListFilterScope.predefinedFilterKeyMapping. Unknown keys are logged and dropped.
  • The predefinedFilterKeyMapping / predefinedSortingKeyMapping registries are generated by Sourcery from the channel-list FilterKey / ChannelListSortingKey declarations (Generated/PredefinedFilter+Generated.swift). make generate regenerates them and CI (validate_generated_code) fails if the committed output is stale.
  • ChannelListPayload decodes the new predefined_filter response key. ChannelListUpdater persists the resolved filter+sort onto ChannelListQueryDTO and returns an updated ChannelListQuery to the controller/state layer.
  • On controller/state init, loadPredefinedFilter(for:) rehydrates the locally cached resolved query so Core Data fetch predicates work before the first server response lands. Channels are linked and the fetch request matches on query.queryHash.

v4 adaptations

  • State layer keeps v4's StreamCollection<ChatChannel> channels and lazy var state.
  • DB lookup uses channelListQuery(_ query:) / ChannelListQueryDTO.load(query:) keyed on queryHash.
  • No StreamCore dependency (v4 predates it); the v5-only grouped-channels code is excluded.

🧪 Manual Testing Notes

  • The demo app's channel list filter picker exposes "Messaging (Predefined)", "Archived (Predefined)" and "Hidden (Predefined)" entries — pick one and verify the channel list loads, paginates, and updates on websocket events.
  • For a plain (non-predefined) query, behaviour should be unchanged.

New pre-defined filters can be added through the dashboard.

☑️ 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

Backport of #4113 to the v4 release line.

Reference a server-side predefined channel-list filter by name and let the
server resolve its filter/sort templates instead of constructing the filter
client-side. Adds ChannelListQuery(predefinedFilter:filterValues:sortValues:),
decodes the predefined_filter response, caches the resolved filter/sort on
ChannelListQueryDTO, and re-applies it locally.

Adapted for v4: StreamCollection-based state layer, channelListQuery(_:)
lookup, no StreamCore dependency. Includes the Sourcery codegen pipeline
(make generate + CI validate_generated_code) and demo app query samples.
@laevandus laevandus requested a review from a team as a code owner June 2, 2026 13:42
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

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: 06e558f4-018b-41ee-b682-b589f61a44a5

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 v4-predefined-filters

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.

@laevandus laevandus added the ✅ Feature An issue or PR related to a feature label Jun 2, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

1 Warning
⚠️ Big PR

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.61 ms per s 34.75% 🔼 🟢
Frame rate 75 fps 78.36 fps 4.48% 🔼 🟢
Number of hitches 1 0.6 40.0% 🔼 🟢

Copy link
Copy Markdown
Contributor

@martinmitrevski martinmitrevski left a comment

Choose a reason for hiding this comment

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

lgtm, just have one question (see comments). Also, should we run a qa here as well?

Comment thread DemoApp/StreamChat/StreamChatWrapper+DemoApp.swift Outdated
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Public Interface

 public struct ChannelListQuery: Encodable, LocalConvertibleSortingQuery  
-   
+   case predefinedFilter = "predefined_filter"
- 
+   case filterValues = "filter_values"
-   public let filter: Filter<ChannelListFilterScope>
+   case sortValues = "sort_values"
-   public let sort: [Sorting<ChannelListSortingKey>]
+   
-   public var pagination: Pagination
+ 
-   public let messagesLimit: Int?
+   public internal var filter: Filter<ChannelListFilterScope>
-   public let membersLimit: Int?
+   public internal var sort: [Sorting<ChannelListSortingKey>]
-   public var options: QueryOptions
+   public var pagination: Pagination
-   
+   public let messagesLimit: Int?
- 
+   public let membersLimit: Int?
-   public init(filter: Filter<ChannelListFilterScope>,sort: [Sorting<ChannelListSortingKey>] = [],pageSize: Int = .channelsPageSize,messagesLimit: Int? = nil,membersLimit: Int? = nil)
+   public var options: QueryOptions
-   
+   public let predefinedFilter: String?
- 
+   public let filterValues: [String: RawJSON]?
-   public func encode(to encoder: Encoder)throws
+   public let sortValues: [String: RawJSON]?
+   
+ 
+   public init(filter: Filter<ChannelListFilterScope>,sort: [Sorting<ChannelListSortingKey>] = [],pageSize: Int = .channelsPageSize,messagesLimit: Int? = nil,membersLimit: Int? = nil)
+   public init(predefinedFilter: String,filterValues: [String: RawJSON]? = nil,sortValues: [String: RawJSON]? = nil,pageSize: Int = .channelsPageSize,messagesLimit: Int? = nil,membersLimit: Int? = nil)
+   
+ 
+   public func encode(to encoder: Encoder)throws

 @MainActor public final class ChannelListState: ObservableObject  
-   public let query: ChannelListQuery
+   public internal var query: ChannelListQuery

 public class ChatChannelListController: DataController, DelegateCallable, DataStoreProvider  
-   public let query: ChannelListQuery
+   public internal var query: ChannelListQuery

@Stream-SDK-Bot
Copy link
Copy Markdown
Collaborator

SDK Size

title v4 branch diff status
StreamChat 8.62 MB 8.69 MB +73 KB 🟢
StreamChatUI 4.94 MB 4.94 MB 0 KB 🟢

@Stream-SDK-Bot
Copy link
Copy Markdown
Collaborator

StreamChat XCSize

Object Diff (bytes)
ChannelListController.o +48305
UserListController.o -40658
PredefinedFilter+Generated.o +18395
ChannelListQuery+PredefinedFilter.o +8559
ChannelListQueryDTO.o +7013
Show 39 more objects
Object Diff (bytes)
ChannelListPayload.o +6260
ChannelListUpdater.o +4634
ChannelListQuery.o +4629
Filter.o +3883
ChannelListState+Observer.o +3689
ChannelList.o +2292
UserListQuery.o +1732
ChannelDTO.o +1662
ThreadDTO.o -1436
ChatMessage.o +1428
ThreadListQuery.o +1336
LivestreamChannelController.o +1304
Chat.o -781
CurrentUserController.o +466
CurrentUserPayloads.o -332
RawJSON.o +312
RequestEncoder.o +280
SyncOperations.o +264
ChannelUpdater.o +232
ChannelController.o +164
UserController.o -156
FlagUserPayload.o -132
PollsPayloads.o -124
MemberList.o +124
CastPollVoteRequestBody.o +108
MemberListController.o -96
AttachmentDownloader.o +91
ChannelListSortingKey.o -84
MemberEventMiddleware.o -83
ThreadListController.o -80
DatabaseSession.o +80
ReactionListController.o -80
PollController.o -80
APIClient.o +68
ListChange.o -64
DraftPayloads.o -64
ChannelListState.o +56
UserListUpdater.o +52
ChannelListLinker.o +48

@Stream-SDK-Bot
Copy link
Copy Markdown
Collaborator

StreamChatUI XCSize

Object Diff (bytes)
ChatChannelListRouter.o -44

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 3, 2026

@testableapple testableapple added 🟢 QAed A PR that was QAed and removed 🧪 QAing labels Jun 3, 2026
@laevandus laevandus merged commit 96487a0 into v4 Jun 3, 2026
15 checks passed
@laevandus laevandus deleted the v4-predefined-filters branch June 3, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✅ Feature An issue or PR related to a feature 🟢 QAed A PR that was QAed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants