Skip to content

Add LivestreamChannelController #3750

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 87 commits into from
Aug 6, 2025
Merged

Conversation

nuno-vieira
Copy link
Member

@nuno-vieira nuno-vieira commented Jul 25, 2025

🔗 Issue Links

https://linear.app/stream/issue/IOS-1025/create-a-livestream-controller

🎯 Goal

Create a new LivestreamChannelController which is a controller suitable for Livestreams due to better performance. It does not rely on DB persistence. But has fewer features than a regular channel controller.

📝 Summary

  • LivestreamChannelController handles events manually instead of relying on Event Middlewares and DB updates
  • Adds Payload -> Model mapping
  • Adds a DemoLivestreamChatChannelVC in the Demo App to test drive the controller
  • Adds a way for discarding older messages automatically:
    • Configurable through LivestreamChannelController.maxMessageLimitOptions
    • Exposes functions to handle pagination while discarding older messages:
      • pause() that will stop collecting messages in memory on message.new event
      • resume() that will load the first page and start collecting messages again on message.new
      • When createNewMessage() is called, it resumes automatically if it was paused.

Performance Benefits

The LivestreamChannelController is optimised for livestream scenarios:

  • More performant and more efficient - No database overhead, which reduces CPU usage
  • Lower memory usage - Messages are not saved into the DB, and older in-memory messages are automatically discarded once they reach a defined limit

Comparison with Regular Chat

Feature Regular Chat Live Chat
Performance Good Excellent
Memory Usage Higher Lower
Messages Capping
Reactions
Attachments Rendering
Attachment Upload Progress
Commands
Read Indicators
Typing Indicators
Message Editing
Threads
Offline Support

Current Issues/Limitations

  • The channel list won't re-order for these live channels, since the last message is not saved into the DB. (Workaround is to perform a light-weight fetch of the channel, to trigger re-order on viewWillDisappear)
  • Ephemeral commands are not correctly rendered
  • Attachment upload progress not working (Since it relies on DB updates)
  • When channel is updated, ownCapabilities is removed, which makes the Reactions UI not working. The channel.updated event does not come with ownCapabilities.

🧪 Manual Testing Notes

How to test:

  1. Swipe a channel in the channel list
  2. Tap on "..."
  3. Tap on "Show as Livestream Controller"

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

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced a Livestream Chat Channel experience, including a new in-memory LivestreamChannelController for real-time chat without local persistence.
    • Added a dedicated Livestream Chat UI with message list, composer, reactions, and pinning support.
    • Provided custom message actions and a SwiftUI-based reactions list for livestream messages.
    • Exposed Combine publishers for livestream channel state updates.
    • Enabled manual event handling for channels opting out of middleware processing.
    • Added a new alert action to show channels as Livestream Controllers.
  • Improvements

    • Enhanced message and channel model conversion from payloads.
    • Added new methods for updating channel and message models selectively.
    • Improved slow mode management with explicit enable/disable and input validation removal.
    • Made various UI and controller methods and properties publicly accessible for greater flexibility.
    • Added support for memory warning notifications to app state observers.
    • Included channel context explicitly in message event processing for better event handling.
  • Bug Fixes

    • Corrected internal logic for disabling slow mode and event processing with channel context.
  • Chores

    • Updated project structure to include new files and organize demo and core components.

Copy link

coderabbitai bot commented Jul 25, 2025

Walkthrough

This pull request introduces a new LivestreamChannelController for managing in-memory livestream chat channels, including all supporting model conversion extensions, Combine publishers, and event handling infrastructure. It adds demo UI components for livestream chat, message actions, and reactions. Internal event and message handling are refactored to support channel context and manual event processing.

Changes

Cohort / File(s) Change Summary
Livestream Controller: Core Implementation
Sources/StreamChat/Controllers/ChannelController/LivestreamChannelController.swift, Sources/StreamChat/Controllers/ChannelController/LivestreamChannelController+Combine.swift
Introduces LivestreamChannelController with in-memory channel/message management, event handling, delegate protocol, and Combine publishers for channel state.
Payload-to-Model Conversion Extensions
Sources/StreamChat/Models/Payload+asModel/ChannelPayload+asModel.swift, Sources/StreamChat/Models/Payload+asModel/MessagePayload+asModel.swift, Sources/StreamChat/Models/Payload+asModel/UserPayload+asModel.swift
Adds extensions to convert API payloads to domain models for channels, messages, reactions, members, reads, and users.
Demo Livestream UI Components
DemoApp/Screens/Livestream/DemoLivestreamChatChannelVC.swift, DemoApp/Screens/Livestream/DemoLivestreamChatMessageListVC.swift, DemoApp/Screens/Livestream/DemoLivestreamMessageActionsVC.swift, DemoApp/Screens/Livestream/DemoLivestreamReactionsListView.swift
Adds demo view controllers and SwiftUI views for livestream chat channel, message list, message actions, and reactions list, integrating with the new controller.
Manual Event Handling Infrastructure
Sources/StreamChat/Workers/ManualEventHandler.swift, Sources/StreamChat/Workers/EventNotificationCenter.swift, Sources/StreamChat/ChatClientFactory.swift
Implements ManualEventHandler for manual event processing, updates event notification center to support manual channels, and adapts factory initialization.
Channel/Message Model Enhancements
Sources/StreamChat/Models/Channel.swift, Sources/StreamChat/Models/ChatMessage.swift
Adds/updates changing methods for selective property updates on ChatChannel and ChatMessage.
Event & Message Flow Refactoring
Sources/StreamChat/Controllers/ChannelController/ChannelController.swift, Sources/StreamChat/Controllers/MessageController/MessageController.swift, Sources/StreamChat/StateLayer/Chat.swift, Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift, Sources/StreamChat/Workers/Background/MessageSender.swift, Sources/StreamChat/Workers/ChannelUpdater.swift
Refactors event and message creation to include channel ID (cid), updates slow mode logic, and improves error event handling.
UI API Surface and Integration
Sources/StreamChatUI/ChatMessageList/ChatMessageListVC+DiffKit.swift, Sources/StreamChatUI/ChatMessageList/ChatMessageListView.swift, Sources/StreamChatUI/ChatMessageList/ScrollToBottomButton.swift, Sources/StreamChatUI/Composer/ComposerVC.swift
Exposes new/public methods and properties for message snapshot management, scroll button content, and composer mention handling, supporting livestream UI needs.
App State Observer Extension
Sources/StreamChat/Audio/AppStateObserving.swift
Adds memory warning notification support to app state observer protocol and implementation.
Project Structure
StreamChat.xcodeproj/project.pbxproj
Adds new files and organizes them within the Xcode project.
User Model Conversion Cleanup
Sources/StreamChat/Workers/UserListUpdater.swift
Removes a now-redundant private extension for user payload conversion.
DemoChatChannelListRouter Livestream Action
DemoApp/StreamChat/Components/DemoChatChannelListRouter.swift
Adds a new alert action to present the livestream chat channel controller from the demo app UI.

Sequence Diagram(s)

sequenceDiagram
    participant UI as DemoLivestreamChatChannelVC
    participant Controller as LivestreamChannelController
    participant API as APIClient
    participant Event as EventsController

    UI->>Controller: synchronize()
    Controller->>API: fetch channel/messages
    API-->>Controller: ChannelPayload/MessagePayload
    Controller->>Controller: Convert payloads to models
    Controller->>UI: delegate.didUpdateChannel / didUpdateMessages

    Event-->>Controller: Event (message/new, message/updated, etc.)
    Controller->>Controller: Update in-memory state
    Controller->>UI: delegate.didUpdateMessages

    UI->>Controller: createNewMessage(...)
    Controller->>API: send message
    API-->>Controller: MessagePayload
    Controller->>Controller: Update messages, notify delegate

    UI->>Controller: loadPreviousMessages/loadNextMessages
    Controller->>API: fetch messages
    API-->>Controller: MessagePayload[]
    Controller->>Controller: Merge into messages, notify delegate
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90 minutes

Assessment against linked issues

Objective Addressed Explanation
Create a livestream controller (IOS-1025)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Add memory warning observer to AppStateObserver (Sources/StreamChat/Audio/AppStateObserving.swift) This enhancement is unrelated to the livestream controller objective; it extends app state observation to handle memory warnings.
Expose/remove mention user method in ComposerVC (Sources/StreamChatUI/Composer/ComposerVC.swift) Changing access for mention user removal is not required for the livestream controller and appears tangential.
Add public access to ScrollToBottomButton content (Sources/StreamChatUI/ChatMessageList/ScrollToBottomButton.swift) Making this property public is not directly related to the livestream controller objective.
Add memory snapshot array support to ChatMessageListView (Sources/StreamChatUI/ChatMessageList/ChatMessageListView.swift) While it supports the new livestream UI, the objective did not explicitly call for UI changes; inclusion may be justified but is not strictly in scope.
Add memory warning notification support (Sources/StreamChat/Audio/AppStateObserving.swift) Not related to the livestream controller objective.

Suggested labels

🌐 SDK: StreamChat (LLC), ✅ Feature, 🟢 QAed

Suggested reviewers

  • laevandus

Poem

A rabbit hops in streaming glee,
New channels live, in-memory!
With models fresh and payloads neat,
Events and messages quickly meet.
The UI bounces, chat flows fast—
Livestream magic built to last!
🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52efeee and 5153c46.

📒 Files selected for processing (3)
  • Sources/StreamChat/Workers/ManualEventHandler.swift (1 hunks)
  • StreamChat.xcodeproj/project.pbxproj (30 hunks)
  • Tests/StreamChatTests/Workers/ManualEventHandler_Tests.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • Tests/StreamChatTests/Workers/ManualEventHandler_Tests.swift
  • StreamChat.xcodeproj/project.pbxproj
  • Sources/StreamChat/Workers/ManualEventHandler.swift
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test LLC (Debug)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add/livestream-channel-controller

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@nuno-vieira nuno-vieira changed the title Add/livestream channel controller Add LivestreamChannelController Jul 25, 2025
@Stream-SDK-Bot
Copy link
Collaborator

Stream-SDK-Bot commented Jul 25, 2025

SDK Size

title develop branch diff status
StreamChat 7.92 MB 8.04 MB +125 KB 🟢
StreamChatUI 4.84 MB 4.86 MB +17 KB 🟢

Copy link
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.

Looks good so far! From the testing, I think we should be more aggressive with fetching the next page (do it sooner), there's a bit of delay while loading more messages. Also, we should think about the initial blank state when opening the chat - since there's no local data available now - do we expose some loading state?

Copy link
Contributor

@laevandus laevandus left a comment

Choose a reason for hiding this comment

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

I am good with these changes. ✅

@nuno-vieira nuno-vieira added 🌐 SDK: StreamChat (LLC) Tasks related to the StreamChat LLC SDK ✅ Feature An issue or PR related to a feature 🤞 Ready For QA A PR that is Ready for QA labels Aug 5, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift (3)

118-121: Use self.controller for consistency

For consistency with the rest of the test file, consider using self.controller instead of just controller.

-controller = LivestreamChannelController(
+self.controller = LivestreamChannelController(
     channelQuery: channelQuery,
     client: client
 )

807-807: Consider consistent cleanup pattern for mock objects

The mockUpdater.cleanUp() is called in these tests but not in other tests that use mocks. Consider either:

  1. Adding cleanup for all mock objects consistently across all tests
  2. Relying on the tearDown method for cleanup
  3. Using defer { mockUpdater.cleanUp() } right after creating the mock

Also applies to: 859-859


1765-1769: Potential test flakiness due to time-based assertions

The hardcoded time range assertions (18-22 seconds) could cause flaky tests in CI environments where timing might vary. Consider using a more flexible tolerance or mocking the current date.

 // Then
 // Should be approximately 20 seconds (30 - 10)
-XCTAssertGreaterThan(cooldownTime, 18)
-XCTAssertLessThan(cooldownTime, 22)
+let expectedCooldown = 20
+let tolerance = 5 // Allow 5 seconds tolerance
+XCTAssertEqual(cooldownTime, expectedCooldown, accuracy: tolerance)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62102d8 and 5d53af6.

📒 Files selected for processing (5)
  • Sources/StreamChat/Controllers/ChannelController/LivestreamChannelController.swift (1 hunks)
  • StreamChat.xcodeproj/project.pbxproj (25 hunks)
  • TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/EventNotificationCenter_Mock.swift (1 hunks)
  • Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift (1 hunks)
  • Tests/StreamChatTests/Workers/ChannelUpdater_Tests.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • StreamChat.xcodeproj/project.pbxproj
  • Sources/StreamChat/Controllers/ChannelController/LivestreamChannelController.swift
🧰 Additional context used
🧬 Code Graph Analysis (1)
TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/EventNotificationCenter_Mock.swift (1)
Sources/StreamChat/Workers/EventNotificationCenter.swift (2)
  • registerManualEventHandling (35-37)
  • unregisterManualEventHandling (40-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Test App and Frameworks
  • GitHub Check: Build LLC + UI (Xcode 15)
  • GitHub Check: Metrics
🔇 Additional comments (4)
Tests/StreamChatTests/Workers/ChannelUpdater_Tests.swift (1)

1773-1807: LGTM! Well-structured test suite for disableSlowMode functionality.

The new test suite properly covers all aspects of the disableSlowMode method:

  1. API Call Verification: Correctly verifies that disabling slow mode calls the enable slow mode endpoint with cooldownDuration: 0
  2. Success Response Handling: Properly tests that successful API responses are propagated to the completion handler
  3. Error Response Handling: Appropriately tests that API errors are propagated to the completion handler

The implementation follows the established testing patterns in the file and provides comprehensive coverage for the new functionality.

TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/EventNotificationCenter_Mock.swift (2)

19-24: LGTM! Consistent with existing mock patterns.

The tracking variables follow the established naming conventions and patterns used throughout this mock class. Using optional ChannelId? is appropriate for tracking the last called parameter.


25-33: LGTM! Proper mock implementation.

The override methods correctly implement mock behavior by tracking call counts and parameters without invoking the parent implementation. This follows the established pattern for other mock methods in this class and aligns with the actual methods being mocked as shown in the relevant code snippets.

Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift (1)

29-43: Memory leak prevention looks good

The tearDown method properly cleans up resources and uses AssertAsync to verify memory deallocation, which is a best practice for preventing retain cycles in test suites.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController+Combine_Tests.swift (2)

64-78: Ensure weak reference is valid before use.

The test sets livestreamChannelController = nil and then immediately uses the weak reference controller. Consider adding a nil check to make the test more robust.

 // Keep only the weak reference to the controller. The existing publisher should keep it alive.
 weak var controller: LivestreamChannelController? = livestreamChannelController
 livestreamChannelController = nil

 // Simulate channel update event
-controller?.eventsController(
-    EventsController(notificationCenter: client.eventNotificationCenter),
-    didReceiveEvent: event
-)
+guard let controller = controller else {
+    XCTFail("Controller should be kept alive by publisher")
+    return
+}
+controller.eventsController(
+    EventsController(notificationCenter: client.eventNotificationCenter),
+    didReceiveEvent: event
+)

168-193: Consider testing resume functionality as well.

The isPaused publisher test covers the pause scenario well. Consider adding a test for the resume functionality to ensure complete coverage of the pause/resume cycle.

Add this test after the pause verification:

 // Test pausing
 controller?.pause()
 
 // Use AssertAsync to wait for the async update
 AssertAsync {
     Assert.willBeEqual(recording.output, [false, true])
     Assert.willBeEqual(controller?.isPaused, true)
 }

+// Test resuming
+controller?.resume()
+
+AssertAsync {
+    Assert.willBeEqual(recording.output, [false, true, false])
+    Assert.willBeEqual(controller?.isPaused, false)
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c78a475 and e48080f.

📒 Files selected for processing (2)
  • StreamChat.xcodeproj/project.pbxproj (26 hunks)
  • Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController+Combine_Tests.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • StreamChat.xcodeproj/project.pbxproj
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test LLC (Debug)
  • GitHub Check: Build Test App and Frameworks
  • GitHub Check: Build LLC + UI (Xcode 15)
  • GitHub Check: Metrics
🔇 Additional comments (3)
Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController+Combine_Tests.swift (3)

11-38: Well-structured test setup with proper memory management.

The test class setup follows excellent testing practices with comprehensive resource cleanup and memory leak detection using AssertAsync.canBeReleased.


98-164: Comprehensive message publisher testing.

The message changes publisher tests properly verify both functionality and memory management. The test correctly simulates multiple message events and validates the expected output count.


212-257: Excellent test of skipped message business logic.

The test properly validates the key business rule that skipped messages are only counted when the controller is paused and the messages are from other users. The feature flag configuration and event simulation are correctly implemented.

@nuno-vieira nuno-vieira force-pushed the add/livestream-channel-controller branch from 52efeee to 5153c46 Compare August 6, 2025 01:41
Copy link

sonarqubecloud bot commented Aug 6, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
3.4% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@testableapple testableapple added 🟢 QAed A PR that was QAed and removed 🤞 Ready For QA A PR that is Ready for QA labels Aug 6, 2025
@nuno-vieira nuno-vieira merged commit e84e41f into develop Aug 6, 2025
12 of 14 checks passed
@nuno-vieira nuno-vieira deleted the add/livestream-channel-controller branch August 6, 2025 09:18
@Stream-SDK-Bot Stream-SDK-Bot mentioned this pull request Aug 6, 2025
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 🌐 SDK: StreamChat (LLC) Tasks related to the StreamChat LLC SDK
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants