Skip to content

fix(ios): resolve UIScene lifecycle crash in plugin registration#158

Open
binSaed wants to merge 1 commit intojitsi:mainfrom
binSaed:fix/uiscene-lifecycle-compatibility
Open

fix(ios): resolve UIScene lifecycle crash in plugin registration#158
binSaed wants to merge 1 commit intojitsi:mainfrom
binSaed:fix/uiscene-lifecycle-compatibility

Conversation

@binSaed
Copy link
Copy Markdown

@binSaed binSaed commented Feb 26, 2026

The plugin was force-unwrapping UIApplication.shared.delegate?.window??.rootViewController during register(with:), which is nil in UIScene lifecycle because the window is managed by the SceneDelegate, not the AppDelegate.

Replace the eager rootViewController resolution at registration time with a lazy computed property that resolves at use-time via UIScene APIs, with fallback to the legacy window approach for pre-iOS 13.

fix #154

The plugin was force-unwrapping UIApplication.shared.delegate?.window??.rootViewController
during register(with:), which is nil in UIScene lifecycle because the window
is managed by the SceneDelegate, not the AppDelegate.

Replace the eager rootViewController resolution at registration time with a
lazy computed property that resolves at use-time via UIScene APIs, with
fallback to the legacy window approach for pre-iOS 13.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 26, 2026 17:11
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a critical iOS crash that occurred during plugin registration when apps use the UIScene lifecycle (iOS 13+). The original implementation force-unwrapped UIApplication.shared.delegate?.window??.rootViewController at registration time, which is nil in UIScene-based apps where windows are managed by SceneDelegate. The fix replaces the eager initialization with a lazy computed property that resolves the root view controller at use-time, with proper fallbacks for different iOS versions.

Changes:

  • Removed stored flutterViewController property and its initialization in the plugin constructor
  • Added lazy computed rootViewController property with iOS 15.0+, iOS 13.0+, and legacy fallback paths
  • Added error handling for when root view controller is unavailable

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +16 to +19
return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }?.rootViewController
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

On iOS 13-14, if there are multiple windows and none of them have isKeyWindow set to true (which can happen in certain scenarios), this will return nil even though valid windows exist. Consider adding a fallback to return the rootViewController from the first window if no key window is found, before falling back to the legacy approach.

Suggested change
return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }?.rootViewController
let windows = UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
if let keyWindow = windows.first(where: { $0.isKeyWindow }) {
return keyWindow.rootViewController
}
if let firstWindow = windows.first {
return firstWindow.rootViewController
}

Copilot uses AI. Check for mistakes.
jitsiMeetViewController!.modalPresentationStyle = .overFullScreen
flutterViewController.present(jitsiMeetViewController!, animated: true)
presenter.present(jitsiMeetViewController!, animated: true)
result("Successfully joined meeting \(room)")
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The success message includes string interpolation of room which is an optional String. If room is nil, this will print "Successfully joined meeting nil". While this doesn't cause a crash, it creates an unclear message. Consider using nil coalescing operator to provide a more meaningful message, or unwrap room safely before interpolation.

Suggested change
result("Successfully joined meeting \(room)")
let successMessage: String
if let roomName = room {
successMessage = "Successfully joined meeting \(roomName)"
} else {
successMessage = "Successfully joined meeting"
}
result(successMessage)

Copilot uses AI. Check for mistakes.
return
}

jitsiMeetViewController = JitsiMeetViewController.init(options: options, eventSink: eventSink!)
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

Force-unwrapping eventSink with the ! operator can cause a crash if eventSink is nil. This can happen if the join method is called before the event channel listener has been set up via onListen. Consider using optional chaining or a guard statement to check if eventSink is available before initializing JitsiMeetViewController, and return an appropriate error if it's nil.

Suggested change
jitsiMeetViewController = JitsiMeetViewController.init(options: options, eventSink: eventSink!)
guard let eventSink = eventSink else {
result(FlutterError(code: "NO_EVENT_SINK", message: "Event sink not available. Ensure the event channel listener is attached before joining.", details: nil))
return
}
jitsiMeetViewController = JitsiMeetViewController.init(options: options, eventSink: eventSink)

Copilot uses AI. Check for mistakes.
Comment on lines 122 to +123
jitsiMeetViewController!.modalPresentationStyle = .overFullScreen
flutterViewController.present(jitsiMeetViewController!, animated: true)
presenter.present(jitsiMeetViewController!, animated: true)
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

Force-unwrapping jitsiMeetViewController with ! can cause a crash if the initialization of JitsiMeetViewController fails. While this is unlikely with the current API, it's safer to use optional chaining or guard statement to handle potential nil values. This also applies to line 123.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +14
return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.first { $0.activationState == .foregroundActive }?
.keyWindow?.rootViewController
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The iOS 15.0+ implementation filters for scenes with activationState equal to foregroundActive. This means if the join method is called when the app is in a different state (like foregroundInactive during a transition), rootViewController will be nil and the join will fail with NO_VIEW_CONTROLLER error. Consider checking for foregroundInactive as well, or removing the activation state filter and just taking the first available scene, with a fallback to check other scenes if needed.

Suggested change
return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.first { $0.activationState == .foregroundActive }?
.keyWindow?.rootViewController
let windowScenes = UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
if let activeScene = windowScenes.first(where: { $0.activationState == .foregroundActive || $0.activationState == .foregroundInactive }),
let rootVC = activeScene.keyWindow?.rootViewController {
return rootVC
}
if let fallbackScene = windowScenes.first,
let rootVC = fallbackScene.keyWindow?.rootViewController {
return rootVC
}

Copilot uses AI. Check for mistakes.
@fabiocody
Copy link
Copy Markdown
Contributor

Closes #152

@fabiocody
Copy link
Copy Markdown
Contributor

@saghul can you have a look at this?

@saghul
Copy link
Copy Markdown
Member

saghul commented Mar 6, 2026

We don't support iOS < 15 in the Jitsi SDK.

@binSaed
Copy link
Copy Markdown
Author

binSaed commented Mar 9, 2026

@saghul
my minimum IOS version is '15.5.0'
and app crash when in run ios simulator 26 with flutter version 3.41.4 channel Stable

image

@saghul
Copy link
Copy Markdown
Member

saghul commented Mar 10, 2026

What I meant is that you can drop the compatibility code for older versions from your PR.

@mayankprojecttree
Copy link
Copy Markdown

@saghul Please review this PR; my iOS app is crashing because of this issue

@Calinteodor
Copy link
Copy Markdown
Contributor

What I meant is that you can drop the compatibility code for older versions from your PR.

@binSaed is it ready to be tested? We are getting ready for a new release/

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.

Flutter iOS app crashes at startup after integrated screen sharing feature following the steps from the official documentation.

6 participants