How to fix the ‘Fatal error: Cannot find the keyWindow. Make sure to call window.makeKeyAndVisible()’ error in React Native/Expo after upgrading?
I’m encountering this error after upgrading my Expo and React Native versions:
EXDevLauncher/ExpoDevLauncherAppDelegateSubscriber.swift:8: Fatal error: Cannot find the keyWindow. Make sure to call window.makeKeyAndVisible().
I’ve tried modifying the AppDelegate.m file without success. Below are my AppDelegate and SceneDelegate files:
AppDelegate.m:
#import <RCTAppDelegate.h>
#import <UIKit/UIKit.h>
#import <Expo/Expo.h>
@interface AppDelegate : EXAppDelegateWrapper
@end
AppDelegate.m (implementation):
#import "AppDelegate.h"
// @generated begin react-native-maps-import - expo prebuild (DO NOT MODIFY) sync-f2f83125c99c0d74b42a2612947510c4e08c423a
#if __has_include(<GoogleMaps/GoogleMaps.h>)
#import <GoogleMaps/GoogleMaps.h>
#endif
// @generated end react-native-maps-import
#import <React/RCTBundleURLProvider.h>
#import <React/RCTLinkingManager.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#if __has_include(<GoogleMaps/GoogleMaps.h>)
[GMSServices provideAPIKey:@"AIzaSyCDnK85Y_BEl8g-tdrdSl8eC2VGotnEB5k"];
#endif
self.moduleName = @"main";
self.initialProps = @{};
NSLog(@"[AppDelegate] didFinishLaunching begin");
// Call super but WITHOUT creating a window (SceneDelegate will do that)
BOOL result = [super application:application didFinishLaunchingWithOptions:launchOptions];
NSLog(@"[AppDelegate] didFinishLaunching end");
return result;
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
return [super application:application didFailToRegisterForRemoteNotificationsWithError:error];
}
// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}
@end
main.m:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
SceneDelegate.m:
#import "SceneDelegate.h"
#import <EXDevLauncher/EXDevLauncherController.h>
#import <React/RCTBridge.h>
#import <React/RCTRootView.h>
#import "AppDelegate.h"
@implementation SceneDelegate
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
if (![scene isKindOfClass:[UIWindowScene class]]) { return; }
UIWindowScene *windowScene = (UIWindowScene *)scene;
NSLog(@"[SceneDelegate] willConnectToSession");
// Create window for this scene
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
NSLog(@"[SceneDelegate] window created");
// Get AppDelegate for dev launcher delegate
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
// Make window visible AFTER dev launcher is initialized
[self.window makeKeyAndVisible];
NSLog(@"[SceneDelegate] window makeKeyAndVisible called");
// NOW start dev launcher with the window
NSLog(@"[SceneDelegate] EXDevLauncherController startWithWindow called");
EXDevLauncherController *controller = [EXDevLauncherController sharedInstance];
[controller startWithWindow:self.window delegate:appDelegate launchOptions:nil];
NSLog(@"[SceneDelegate] setup complete, window is key and visible");
}
- (void)sceneDidDisconnect:(UIScene *)scene {
NSLog(@"[SceneDelegate] sceneDidDisconnect");
}
- (void)sceneDidBecomeActive:(UIScene *)scene {
NSLog(@"[SceneDelegate] sceneDidBecomeActive");
}
- (void)sceneWillResignActive:(UIScene *)scene {
NSLog(@"[SceneDelegate] sceneWillResignActive");
}
- (void)sceneWillEnterForeground:(UIScene *)scene {
NSLog(@"[SceneDelegate] sceneWillEnterForeground");
}
- (void)sceneDidEnterBackground:(UIScene *)scene {
NSLog(@"[SceneDelegate] sceneDidEnterBackground");
}
@end
AppDelegate+SceneConfiguration.m:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "SceneDelegate.h"
// This category supplies a UIScene configuration at runtime so the app
// uses SceneDelegate even if Info.plist doesn't declare UIApplicationSceneManifest.
@implementation AppDelegate (SceneConfiguration)
- (UISceneConfiguration *)application:(UIApplication *)application
configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession
options:(UISceneConnectionOptions *)options
{
UISceneConfiguration *config = [UISceneConfiguration configurationWithName:@"Default Configuration"
sessionRole:connectingSceneSession.role];
config.delegateClass = [SceneDelegate class];
return config;
}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions
{
// No-op. Implemented to fully adopt UIScene lifecycle.
}
@end
What is causing this error and how can I fix it in my React Native/Expo application after upgrading?
After upgrading your Expo and React Native versions, the “Fatal error: Cannot find the keyWindow” typically occurs due to timing issues between SceneDelegate initialization and EXDevLauncher’s attempt to access the window. This is a common problem when migrating to iOS 13+ Scene-based architecture, where the window lifecycle has changed significantly.
Contents
- Understanding the Root Cause
- Immediate Solution: Fixing SceneDelegate Timing
- Alternative Workarounds
- Prevention and Future-Proofing
- Advanced Troubleshooting
Understanding the Root Cause
The error occurs because EXDevLauncher expects to find an active keyWindow during initialization, but in modern iOS Scene-based architecture, the window isn’t immediately available. According to the Expo GitHub issue #23536, this happens specifically when UIApplicationSceneManifest is defined in your Info.plist for multiscreen support or CarPlay compatibility.
The core problem is a race condition between:
- SceneDelegate creating the window
- EXDevLauncher trying to access that window
- The
makeKeyAndVisible()call timing
Key insight: The error message specifically points to
EXDevLauncher/ExpoDevLauncherAppDelegateSubscriber.swift:8, indicating this is happening in Expo’s dev launcher code, not your custom code.
Immediate Solution: Fixing SceneDelegate Timing
The most reliable fix is to ensure the window is properly initialized and made key-and-visible before EXDevLauncher tries to use it. Here’s the corrected SceneDelegate.m:
#import "SceneDelegate.h"
#import <EXDevLauncher/EXDevLauncherController.h>
#import <React/RCTBridge.h>
#import <React/RCTRootView.h>
#import "AppDelegate.h"
@implementation SceneDelegate
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
if (![scene isKindOfClass:[UIWindowScene class]]) { return; }
UIWindowScene *windowScene = (UIWindowScene *)scene;
NSLog(@"[SceneDelegate] willConnectToSession");
// Create window for this scene
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
// CRITICAL: Make window visible BEFORE dev launcher initialization
[self.window makeKeyAndVisible];
NSLog(@"[SceneDelegate] window made key and visible");
// Get AppDelegate for dev launcher delegate
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
// Now start dev launcher with the already-visible window
NSLog(@"[SceneDelegate] EXDevLauncherController startWithWindow called");
EXDevLauncherController *controller = [EXDevLauncherController sharedInstance];
[controller startWithWindow:self.window delegate:appDelegate launchOptions:nil];
NSLog(@"[SceneDelegate] setup complete");
}
Key changes made:
- Moved
makeKeyAndVisible()call before EXDevLauncher initialization - Removed potential race condition by ensuring the window is ready first
- Simplified the flow to avoid any intermediate states
Alternative Workarounds
Method 1: Delayed EXDevLauncher Initialization
If the above doesn’t work, add a small delay:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
EXDevLauncherController *controller = [EXDevLauncherController sharedInstance];
[controller startWithWindow:self.window delegate:appDelegate launchOptions:nil];
});
Method 2: Check for Existing Windows
Add a safety check in your AppDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Check if we have a valid key window
UIWindow *keyWindow = UIApplication.sharedApplication.keyWindow;
if (!keyWindow) {
// Create a temporary window if none exists
UIWindowScene *windowScene = [UIApplication.sharedApplication.connectedScenes anyObject];
if ([windowScene isKindOfClass:[UIWindowScene class]]) {
keyWindow = [[UIWindow alloc] initWithWindowScene:(UIWindowScene *)windowScene];
[keyWindow makeKeyAndVisible];
}
}
// Continue with normal initialization
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
Method 3: Update Expo Dependencies
According to the Expo upgrade saga, ensure you’re using compatible versions:
npx expo install expo@latest expo-dev-client@latest
Prevention and Future-Proofing
1. Use Modern iOS Architecture
Ensure your app fully adopts SceneDelegate pattern:
Info.plist should include:
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
2. Handle Window Lifecycle Properly
Add window lifecycle management:
- (void)sceneDidBecomeActive:(UIScene *)scene {
if ([scene isKindOfClass:[UIWindowScene class]]) {
[self.window makeKeyAndVisible];
}
}
3. Monitor Expo Updates
Subscribe to Expo GitHub issues for similar problems. The community often provides patches before official releases.
Advanced Troubleshooting
If you’re still experiencing issues:
Check for Conflicting Libraries
Some libraries might interfere with window initialization. Temporarily remove third-party libraries to isolate the issue.
Debug with Breakpoints
Add breakpoints in SceneDelegate.m to trace the exact sequence:
// Add these to willConnectToSession
NSLog(@"[DEBUG] About to create window");
// Breakpoint here
NSLog(@"[DEBUG] Window created: %@", self.window);
// Breakpoint here
NSLog(@"[DEBUG] About to make key and visible");
// Breakpoint here
Verify Xcode Project Settings
Ensure your Xcode project has:
- Deployment Target: iOS 13.0+
- Application Scene Manifest: Enabled
- Uses Scenes: Yes
Clean and Rebuild
npx expo start --clear
rm -rf ios/build
cd ios && xcodebuild clean -scheme YourApp -configuration Debug
Conclusion
The “Cannot find the keyWindow” error after upgrading Expo is typically caused by timing issues in the Scene-based architecture. The most effective solution is to ensure your window is properly initialized and made visible before EXDevLauncher attempts to use it.
Key takeaways:
- Move
makeKeyAndVisible()call before EXDevLauncher initialization in SceneDelegate - Ensure proper Info.plist configuration with UIApplicationSceneManifest
- Keep Expo dependencies updated to the latest compatible versions
- Add proper error handling for window lifecycle events
- Monitor community discussions for similar issues and workarounds
If you continue to experience problems, consider checking the Expo GitHub issues or reaching out to the community on Reddit r/reactnative for specific version troubleshooting.
Sources
- Expo GitHub Issue #23536 - expo-dev-launcher breaks with UIApplicationSceneManifest
- Medium - Expo Modules Upgrade Saga (Expo 51 → 53)
- Stack Overflow - How to fix keywindow after upgrading expo and react
- Apple Developer Forums - iOS 15: UIApplication.shared.keyWindow is nil
- Stack Overflow - ‘keyWindow’ was deprecated in iOS 13.0