1 // Copyright 2018-present 650 Industries. All rights reserved. 2 3 import ExpoModulesCore 4 import EXDevMenu 5 import EXUpdatesInterface 6 7 @objc 8 public class ExpoDevLauncherReactDelegateHandler: ExpoReactDelegateHandler, RCTBridgeDelegate, EXDevLauncherControllerDelegate { 9 @objc 10 public static var enableAutoSetup: Bool = true 11 12 private weak var reactDelegate: ExpoReactDelegate? 13 private var bridgeDelegateHandler: DevClientAppDelegate? 14 private var deferredRootView: EXDevLauncherDeferredRCTRootView? 15 private var rootViewModuleName: String? 16 private var rootViewInitialProperties: [AnyHashable : Any]? 17 static var shouldEnableAutoSetup: Bool = { 18 // if someone else has set this explicitly, use that value 19 if !enableAutoSetup { 20 return false 21 } 22 23 if !EXAppDefines.APP_DEBUG { 24 return false 25 } 26 27 // Backwards compatibility -- if the main AppDelegate has already set up expo-dev-launcher, 28 // we just skip in this case. 29 if EXDevLauncherController.sharedInstance().isStarted { 30 return false 31 } 32 33 return true 34 }() 35 createBridgenull36 public override func createBridge(reactDelegate: ExpoReactDelegate, bridgeDelegate: RCTBridgeDelegate, launchOptions: [AnyHashable : Any]?) -> RCTBridge? { 37 if !ExpoDevLauncherReactDelegateHandler.shouldEnableAutoSetup { 38 return nil 39 } 40 41 // DevLauncherController will handle dev menu configuration, so dev menu auto-setup is not needed 42 ExpoDevMenuReactDelegateHandler.enableAutoSetup = false 43 44 self.reactDelegate = reactDelegate 45 self.bridgeDelegateHandler = EXRCTAppDelegateInterceptor(bridgeDelegate: bridgeDelegate, interceptor: self) 46 47 EXDevLauncherController.sharedInstance().autoSetupPrepare(self, launchOptions: launchOptions) 48 if let sharedController = UpdatesControllerRegistry.sharedInstance.controller { 49 // for some reason the swift compiler and bridge are having issues here 50 EXDevLauncherController.sharedInstance().updatesInterface = sharedController 51 } 52 return EXDevLauncherDeferredRCTBridge(delegate: self.bridgeDelegateHandler, launchOptions: launchOptions) 53 } 54 createRootViewnull55 public override func createRootView(reactDelegate: ExpoReactDelegate, bridge: RCTBridge, moduleName: String, initialProperties: [AnyHashable : Any]?) -> RCTRootView? { 56 if !ExpoDevLauncherReactDelegateHandler.shouldEnableAutoSetup { 57 return nil 58 } 59 60 self.rootViewModuleName = moduleName 61 self.rootViewInitialProperties = initialProperties 62 self.deferredRootView = EXDevLauncherDeferredRCTRootView(bridge: bridge, moduleName: moduleName, initialProperties: initialProperties) 63 return self.deferredRootView 64 } 65 66 // MARK: RCTBridgeDelegate implementations 67 68 // swiftlint:disable implicitly_unwrapped_optional sourceURLnull69 public func sourceURL(for bridge: RCTBridge!) -> URL! { 70 return EXDevLauncherController.sharedInstance().sourceUrl() 71 } 72 // swiftlint:enable implicitly_unwrapped_optional 73 74 // MARK: EXDevelopmentClientControllerDelegate implementations 75 devLauncherControllernull76 public func devLauncherController(_ developmentClientController: EXDevLauncherController, didStartWithSuccess success: Bool) { 77 guard let bridgeDelegateHandler = self.bridgeDelegateHandler else { 78 fatalError("bridgeDelegateHandler is not initialized") 79 } 80 let bridge = bridgeDelegateHandler.createBridgeAndSetAdapter(launchOptions: developmentClientController.getLaunchOptions()) 81 developmentClientController.appBridge = bridge 82 83 guard let rootView = bridgeDelegateHandler.createRootView( 84 with: bridge, 85 // swiftlint:disable:next force_unwrapping 86 moduleName: self.rootViewModuleName!, 87 initProps: self.rootViewInitialProperties 88 ) else { 89 return 90 } 91 rootView.backgroundColor = self.deferredRootView?.backgroundColor ?? UIColor.white 92 let window = getWindow() 93 94 // NOTE: this order of assignment seems to actually have an effect on behaviour 95 // direct assignment of window.rootViewController.view = rootView does not work 96 guard let rootViewController = self.reactDelegate?.createRootViewController() else { 97 fatalError("Invalid rootViewController returned from ExpoReactDelegate") 98 } 99 rootViewController.view = rootView 100 window.rootViewController = rootViewController 101 window.makeKeyAndVisible() 102 103 // it is purposeful that we don't clean up saved properties here, because we may initialize 104 // several React instances over a single app lifetime and we want them all to have the same 105 // initial properties 106 } 107 108 // MARK: Internals 109 getWindownull110 private func getWindow() -> UIWindow { 111 var window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first 112 if (window == nil) { 113 window = UIApplication.shared.delegate?.window ?? nil 114 } 115 guard let window = window else { 116 fatalError("Cannot find the current window.") 117 } 118 return window 119 } 120 } 121