Skip to content

Conversation

@Brazol
Copy link
Contributor

@Brazol Brazol commented Dec 23, 2025

When a VoIP push notification is received, it may be handled before Flutter fully initializes. This causes a null pointer exception when accessing StreamVideoPushNotificationPlugin.sharedInstance, which is only set during Flutter plugin registration.

This PR extracts StreamVideoCallkitManager to be a singleton that is always available, even before Flutter initializes so that CallKit calls can be handled regardless of Flutter initialization state. Events that occur before Flutter is ready are queued and delivered once Flutter initializes

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Fixed a crash on iOS and macOS that occurred when a VoIP push notification was received before the app completed initialization from a terminated state.

✏️ Tip: You can customize this high-level summary in your review settings.

@Brazol Brazol requested a review from a team as a code owner December 23, 2025 12:18
@coderabbitai
Copy link

coderabbitai bot commented Dec 23, 2025

Walkthrough

This PR refactors iOS push notification handling to implement deferred event processing. The changes introduce late initialization of event handlers with automatic queuing and flushing of events, allowing the system to buffer events before the handler becomes available.

Changes

Cohort / File(s) Summary
Documentation Updates
packages/stream_video_flutter/CHANGELOG.md, packages/stream_video_push_notification/CHANGELOG.md
Added "Upcoming" section documenting a fix for iOS/macOS crash when VoIP push arrives before Flutter initialization completes from terminated state.
Event Queuing & Singleton Refactor
packages/stream_video_push_notification/ios/.../StreamVideoCallkitManager.swift
Converted to deferred-initialization pattern: sharedInstance replaced with shared singleton; eventHandler made optional with new pendingEvents buffer; public initializer removed in favor of private init() and new setEventHandler(_:) method; sendEvent now queues events when handler is absent.
Plugin Singleton Alignment
packages/stream_video_push_notification/ios/.../StreamVideoPushNotificationPlugin.swift
Aligned plugin to singleton pattern: changed sharedInstance from implicitly-unwrapped to optional; replaced public initializer with private init() using StreamVideoCallkitManager.shared; updated internal call routing to use shared manager; introduced devicePushTokenVoIPKey constant for standardized key management.

Sequence Diagram(s)

sequenceDiagram
    participant VoIP as VoIP System
    participant Plugin as StreamVideoPushNotificationPlugin
    participant Manager as StreamVideoCallkitManager
    participant Handler as EventCallbackHandler
    
    rect rgb(230, 245, 250)
    Note over VoIP,Handler: New Flow: Event Queuing
    end
    
    VoIP->>Manager: sendEvent (before handler attached)
    alt No handler attached
        Manager->>Manager: Queue event in pendingEvents
        Note over Manager: Event buffered
    end
    
    Handler->>Manager: setEventHandler(handler)
    activate Manager
    Manager->>Manager: Attach handler
    Manager->>Manager: flushPendingEvents()
    loop For each queued event
        Manager->>Handler: Deliver queued event
    end
    deactivate Manager
    
    VoIP->>Manager: sendEvent (after handler attached)
    Manager->>Handler: Deliver event immediately
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • chore(repo): Release v1.0.0 #1115: Core iOS CallKit/PushNotification refactoring including singleton pattern changes, event queuing logic, and plugin initialization updates that form the basis for this PR's implementation.

Suggested reviewers

  • renefloor
  • xsahil03x

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main architectural change: extracting StreamVideoCallkitManager as an independent singleton, enabling CallKit handling before Flutter initialization.
Description check ✅ Passed The description clearly explains the problem (VoIP push handling before Flutter initialization), the solution (extracting callkit manager as singleton with event queuing), and the intended benefit. However, it lacks detailed implementation specifics and testing instructions as specified in the template.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/extract-callkit-manager-ios

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.

@Brazol Brazol changed the title extract callkit manager to make it independent of flutter initialization fix(ui): extract callkit manager to make it independent of flutter initialization Dec 23, 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: 0

🧹 Nitpick comments (1)
packages/stream_video_push_notification/ios/stream_video_push_notification/Sources/stream_video_push_notification/StreamVideoCallkitManager.swift (1)

45-57: Consider thread synchronization for robustness.

CXProviderDelegate methods may be called on CallKit's internal dispatch queues. The unsynchronized access to eventHandler and pendingEvents could cause a race condition if setEventHandler is called while sendEvent is simultaneously queuing an event. While the window for this race is narrow (only during startup), consider using a serial dispatch queue or lock for these shared resources.

🔎 Example using a serial queue
+    private let eventQueue = DispatchQueue(label: "io.getstream.callkit.events")
+
     private func sendEvent(_ event: String, _ body: [String: Any?]?) {
         if silenceEvents {
             print(event, " silenced")
             return
         }
 
-        guard let eventHandler = eventHandler else {
-            pendingEvents.append((event: event, body: body))
-            return
-        }
-
-        eventHandler.send(event, body ?? [:])
+        eventQueue.async { [weak self] in
+            guard let self = self else { return }
+            guard let handler = self.eventHandler else {
+                self.pendingEvents.append((event: event, body: body))
+                return
+            }
+            handler.send(event, body ?? [:])
+        }
     }

Apply similar synchronization to setEventHandler and flushPendingEvents.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e3e80cd and 973b9d8.

📒 Files selected for processing (4)
  • packages/stream_video_flutter/CHANGELOG.md
  • packages/stream_video_push_notification/CHANGELOG.md
  • packages/stream_video_push_notification/ios/stream_video_push_notification/Sources/stream_video_push_notification/StreamVideoCallkitManager.swift
  • packages/stream_video_push_notification/ios/stream_video_push_notification/Sources/stream_video_push_notification/StreamVideoPushNotificationPlugin.swift
🧰 Additional context used
🧬 Code graph analysis (1)
packages/stream_video_push_notification/ios/stream_video_push_notification/Sources/stream_video_push_notification/StreamVideoCallkitManager.swift (1)
packages/stream_video_push_notification/ios/stream_video_push_notification/Sources/stream_video_push_notification/StreamVideoPushNotificationPlugin.swift (1)
  • send (96-105)
⏰ 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). (8)
  • GitHub Check: stream_video_push_notification
  • GitHub Check: stream_video
  • GitHub Check: stream_video_flutter
  • GitHub Check: stream_video_noise_cancellation
  • GitHub Check: analyze
  • GitHub Check: iOS Build (Dogfooding)
  • GitHub Check: analyze_legacy_version
  • GitHub Check: build
🔇 Additional comments (8)
packages/stream_video_flutter/CHANGELOG.md (1)

1-5: LGTM!

The changelog entry accurately documents the fix for the VoIP push crash when received before Flutter initialization, and follows the existing formatting conventions.

packages/stream_video_push_notification/CHANGELOG.md (1)

1-5: LGTM!

Changelog entry is consistent with the parallel entry in the stream_video_flutter package and correctly documents the iOS/macOS crash fix.

packages/stream_video_push_notification/ios/stream_video_push_notification/Sources/stream_video_push_notification/StreamVideoCallkitManager.swift (3)

9-9: LGTM!

Good use of static let with a private initializer to enforce the singleton pattern. This ensures the manager is always available regardless of Flutter's initialization state.


30-43: Well-designed event queuing mechanism.

The flush-on-attach pattern correctly ensures events received before Flutter initialization are preserved and delivered in order once the handler is wired up.


59-63: LGTM!

The NSDictionary conversion correctly routes Obj-C callers through the unified event handling path with proper nil handling.

packages/stream_video_push_notification/ios/stream_video_push_notification/Sources/stream_video_push_notification/StreamVideoPushNotificationPlugin.swift (3)

5-5: Good extraction of magic string to a constant.

This improves maintainability and prevents typos across the getter/setter methods.


18-33: LGTM! Correct initialization order.

The registration flow properly wires the event handler to the shared manager before creating the plugin instance, ensuring any queued events from early VoIP pushes are flushed to Flutter once the channel is ready.


65-82: LGTM! Key fix for the VoIP push crash.

Static methods now operate directly on StreamVideoCallkitManager.shared, making them safe to call from PKPushRegistry delegate before Flutter plugin registration completes.

@codecov
Copy link

codecov bot commented Dec 23, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 6.31%. Comparing base (6850c9e) to head (973b9d8).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@          Coverage Diff          @@
##            main   #1140   +/-   ##
=====================================
  Coverage   6.31%   6.31%           
=====================================
  Files        595     595           
  Lines      41081   41081           
=====================================
  Hits        2595    2595           
  Misses     38486   38486           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Brazol Brazol merged commit 27673d5 into main Dec 23, 2025
17 of 18 checks passed
@Brazol Brazol deleted the fix/extract-callkit-manager-ios branch December 23, 2025 20:10
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.

3 participants