Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can we keep the AppDelegate swift file when upgrading from React Native 0.66.4 to 0.69.6

We are upgrading a react native project from 0.66.4 to 0.69.6.

The project uses an AppDelegate.swift file but the react native upgrade helper uses the AppDelegate.m class in its upgrade example and instructs to delete it and replace it with a AppDelegate.mm class. See Upgrade helper

To my understanding this allows the compiler to compile C and C++ code.

My question is how do we keep the logic in our AppDelegate swift class? Do we have to use the AppDelegate.mm and integrate the swift code into it or is there a better way?

Content from AppDelegate.swift:

import UIKit
import Firebase
import Adyen
import GoogleCast
import Didomi
import os
import FBSDKCoreKit

#if DEBUG
// #if FB_SONARKIT_ENABLED
import FlipperKit
// #endif
#endif

@UIApplicationMain

  class AppDelegate: UIResponder, RCTBridgeDelegate, UIApplicationDelegate,
  UNUserNotificationCenterDelegate, RNAppAuthAuthorizationFlowManager {

  private func initializeFlipper(with application: UIApplication) {
    #if DEBUG
    // Check if FB_SONARKIT_ENABLED  and commented pluggins work in RN 0.67+ versions
    // #if FB_SONARKIT_ENABLED
    let client = FlipperClient.shared()
    let layoutDescriptorMapper = SKDescriptorMapper(defaults: ())
    // FlipperKitLayoutComponentKitSupport.setUpWith(layoutDescriptorMapper)
    client?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))
    client?.add(FKUserDefaultsPlugin(suiteName: nil))
    client?.add(FlipperKitReactPlugin())
    client?.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter()))
    // client?.add(FlipperReactPerformancePlugin.sharedInstance())
    client?.start()
    // #endif
    #endif
  }

  func sourceURL(for bridge: RCTBridge!) -> URL! {
    #if DEBUG
      return RCTBundleURLProvider.sharedSettings()?.jsBundleURL(forBundleRoot: "index", fallbackResource: nil)
    #else
      return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
    #endif
  }

  var window: UIWindow?
  var bridge: RCTBridge!
  var orientationLock: UIInterfaceOrientationMask = .portrait
  var rootView: RCTRootView?
  var isScreenRecordingEnabled: Bool?
  var isCaptured: Bool?
  public weak var authorizationFlowManagerDelegate: RNAppAuthAuthorizationFlowManagerDelegate?

  func application(_ application: UIApplication,
                   willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
   
    _ = localizedString(LocalizationKey(key: "adyen.card.expiryItem.title"), nil)

    //  Start screen recording in a disabled state
    isScreenRecordingEnabled = false
    let config = ReactNativeConfig.env()!
    WonderPush.setClientId((config["WONDERPUSH_CLIENT_ID"] as? String)!,
                           secret: (config["WONDERPUSH_CLIENT_SECRET"] as? String)!)
    WonderPush.setupDelegateForUserNotificationCenter()
    WonderPush.setRequiresUserConsent(false)
    WonderPush.setUserConsent(true)

    return true
  }
  // swiftlint:disable block_based_kvo
  // swiftlint:disable colon
  override func observeValue(forKeyPath keyPath: String?, of object: Any?,
                             change: [NSKeyValueChangeKey : Any]?,
                             context: UnsafeMutableRawPointer?) {
    if #available(iOS 11.0, *) {
      if keyPath == "captured" {
        isCaptured = UIScreen.main.isCaptured
        if !isScreenRecordingEnabled! {
          if isCaptured! {
            rootView?.isHidden = true
          }
          if !isCaptured! && rootView?.isHidden == true {
            rootView?.isHidden = false
          }
        }
      }
    }
  }

  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // swiftlint:disable force_cast
    launchDidomi(apiKey: ReactNativeConfig.env()!["DIDOMI_API_KEY"] as! String)
    FirebaseApp.configure()
    initializeFlipper(with: application)

    let receiverAppID = kGCKDefaultMediaReceiverApplicationID // or "ABCD1234"
    let criteria = GCKDiscoveryCriteria(applicationID: receiverAppID)
    let options = GCKCastOptions(discoveryCriteria: criteria)
    GCKCastContext.setSharedInstanceWith(options)
    GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

    self.bridge = RCTBridge(delegate: self, launchOptions: launchOptions)
    guard let bridge = self.bridge else {
      return false
    }

    let props: [AnyHashable: Any] = [
      "APP_INFO": (Bundle.main.infoDictionary?["APP_INFO"] as? String) ?? "",
      "VERSION": (Bundle.main.infoDictionary?["CFBundleShortVersionString"]) ?? ""
    ]

    rootView = RCTRootView(bridge: bridge, moduleName: "PSG", initialProperties: props)

    rootView?.backgroundColor = UIColor(red: 22.0/255.0, green: 33.0/255.0, blue: 45.0/255.0, alpha: 1.0)

    self.window = UIWindow(frame: UIScreen.main.bounds)
    let rootViewController = UIViewController()
    rootViewController.view = rootView
    self.window?.rootViewController = rootViewController
    self.window?.makeKeyAndVisible()

    RNSplashScreen.show()

    WonderPush.application(application, didFinishLaunchingWithOptions: launchOptions)
    UIScreen.main.addObserver(self, forKeyPath: "captured", options: .new, context: nil)

    
    NotificationCenter.default.addObserver(self, selector: #selector(self.disableScreenRecording(notification:)), name: Notification.Name("DisableScreenRecording"), object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(self.enableScreenRecording(notification:)), name: Notification.Name("EnableScreenRecording"), object: nil)
    return true
  }

  private func launchDidomi(apiKey: String) {
    Didomi.shared.setLogLevel(minLevel: OSLogType.info.rawValue)

    Didomi.shared.initialize(
      apiKey: apiKey,
      localConfigurationPath: nil,
      remoteConfigurationURL: nil,
      providerId: nil,
      disableDidomiRemoteConfig: false
    )
  }

  @objc func disableScreenRecording(notification: NSNotification) {
    isScreenRecordingEnabled = false
    DispatchQueue.main.async {
      if self.isCaptured ?? false {
        self.rootView?.isHidden = true
      }
    }
  }
  @objc func enableScreenRecording(notification: NSNotification) {
    isScreenRecordingEnabled = true
  }
  func application(
      _ application: UIApplication,
      continue userActivity: NSUserActivity,
      restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
  ) -> Bool {
    // https://firebase.google.com/docs/dynamic-links/ios/receive#open-dynamic-links-in-your-app step 6
    let handled = DynamicLinks.dynamicLinks()
      .handleUniversalLink(userActivity.webpageURL!) { dynamiclink, error in
        _ = dynamiclink
        _ = error
        // no-op
      }

    return handled || RCTLinkingManager.application(
        application,
        continue: userActivity,
        restorationHandler: restorationHandler)
  }
  
  func application(_ app: UIApplication,
                   open url: URL,
                   options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    // https://firebase.google.com/docs/dynamic-links/ios/receive#open-dynamic-links-in-your-app step 7
    if DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) != nil {
     
      return true
    }

    RCTLinkingManager.application(app, open: url, options: options)
    if ApplicationDelegate.shared.application(app, open: url, options: options) {
      return true
    }
    // swiftlint:enable colon
    if Adyen.RedirectComponent.applicationDidOpen(from: url) { return true }

    return authorizationFlowManagerDelegate?.resumeExternalUserAgentFlow(with: url) ?? false
  }

  // swiftlint:disable colon
  func application(_ application: UIApplication,
                   didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
    // swiftlint:enable colon
    WonderPush.application(application,
                           didReceiveRemoteNotification: userInfo)
  }

  func application(_ application: UIApplication,
                   didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    WonderPush.application(application,
                           didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
  }

  func application(_ application: UIApplication,
                   didFailToRegisterForRemoteNotificationsWithError error: Error) {
    WonderPush.application(application,
                           didFailToRegisterForRemoteNotificationsWithError: error)
  }

  // swiftlint:disable colon
  func application(_ application: UIApplication,
                   didReceiveRemoteNotification userInfo: [AnyHashable : Any],
                   fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    // swiftlint:enable colon
    WonderPush.application(application,
                           didReceiveRemoteNotification: userInfo,
                           fetchCompletionHandler: completionHandler)
  }

  func applicationDidBecomeActive(_ application: UIApplication) {
    WonderPush.applicationDidBecomeActive(application)
  }

  func applicationDidEnterBackground(_ application: UIApplication) {
    WonderPush.applicationDidEnterBackground(application)
  }

  func application(_ application: UIApplication,
                   supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return orientationLock
  }
}

How would an AppDelegate.mm file that includes this functionality would look like?

like image 584
Solly Avatar asked Oct 14 '25 21:10

Solly


1 Answers

React Native is experimenting with a new rendering system written in c++. C++ is not directly usable via Swift - and thats why they're recommending using .mm file. .mm file is an Objective c++ file which can have c++ code in it. Using Objective c++ is one way to bridge c++ code to Swift (the other is to write a C wrapper). So to initialize Fabric you need to have the AppDelegate written in objective c++.

If you prefer to work in swift, then we can write a base class in objective c++, and override relevant methods in swift. Something like this should work.

Add a new file: AppDelegateBase.h:

#import <UIKit/UIKit.h>

@interface AppDelegateBase : UIResponder <UIApplicationDelegate>

- (NSDictionary *)prepareInitialProps;

@end

Add another new file: AppDelegateBase.mm - (Based on AppDelegate.mm from react diff project)


#import "AppDelegateBase.h"

#import <React/RCTBundleURLProvider.h>
#import <React/RCTBridge.h>
#import <React/RCTRootView.h>

#import <React/RCTAppSetupUtils.h>

#if RCT_NEW_ARCH_ENABLED
#import <React/CoreModulesPlugins.h>
#import <React/RCTCxxBridgeDelegate.h>
#import <React/RCTFabricSurfaceHostingProxyRootView.h>
#import <React/RCTSurfacePresenter.h>
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <ReactCommon/RCTTurboModuleManager.h>

#import <react/config/ReactNativeConfig.h>

static NSString *const kRNConcurrentRoot = @"concurrentRoot";

@interface AppDelegateBase () <RCTCxxBridgeDelegate, RCTTurboModuleManagerDelegate> {
  RCTTurboModuleManager *_turboModuleManager;
  RCTSurfacePresenterBridgeAdapter *_bridgeAdapter;
  std::shared_ptr<const facebook::react::ReactNativeConfig> _reactNativeConfig;
  facebook::react::ContextContainer::Shared _contextContainer;
}
@end
#endif

@implementation AppDelegateBase

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  RCTAppSetupPrepareApp(application);

  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];

#if RCT_NEW_ARCH_ENABLED
  _contextContainer = std::make_shared<facebook::react::ContextContainer const>();
  _reactNativeConfig = std::make_shared<facebook::react::EmptyReactNativeConfig const>();
  _contextContainer->insert("ReactNativeConfig", _reactNativeConfig);
  _bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:bridge contextContainer:_contextContainer];
  bridge.surfacePresenter = _bridgeAdapter.surfacePresenter;
#endif

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  [self.window makeKeyAndVisible];
  return YES;
}

/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off.
///
/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html
/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture).
/// @return: `true` if the `concurrentRoot` feture is enabled. Otherwise, it returns `false`.
- (BOOL)concurrentRootEnabled
{
  // Switch this bool to turn on and off the concurrent root
  return true;
}

- (NSDictionary *)prepareInitialProps
{
  NSMutableDictionary *initProps = [NSMutableDictionary new];

#ifdef RCT_NEW_ARCH_ENABLED
  initProps[kRNConcurrentRoot] = @([self concurrentRootEnabled]);
#endif

  return initProps;
}

#if RCT_NEW_ARCH_ENABLED

#pragma mark - RCTCxxBridgeDelegate

- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:(RCTBridge *)bridge
{
  _turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge
                                                             delegate:self
                                                            jsInvoker:bridge.jsCallInvoker];
  return RCTAppSetupDefaultJsExecutorFactory(bridge, _turboModuleManager);
}

#pragma mark RCTTurboModuleManagerDelegate

- (Class)getModuleClassFromName:(const char *)name
{
  return RCTCoreModulesClassProvider(name);
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
                                                      jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
  return nullptr;
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
                                                     initParams:
                                                         (const facebook::react::ObjCTurboModule::InitParams &)params
{
  return nullptr;
}

- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
{
  return RCTAppSetupDefaultModuleFromClass(moduleClass);
}

#endif

@end

Update your AppDelegate.swift.. Have marked the changes with >>>.


import UIKit
import Firebase
import Adyen
import GoogleCast
import Didomi
import os
import FBSDKCoreKit

#if DEBUG
// #if FB_SONARKIT_ENABLED
import FlipperKit
// #endif
#endif

@UIApplicationMain
class AppDelegate: AppDelegateBase, // >>> subclassing base class 
UNUserNotificationCenterDelegate, 
RCTBridgeDelegate, 
RNAppAuthAuthorizationFlowManager {

  private func initializeFlipper(with application: UIApplication) {
    #if DEBUG
    // Check if FB_SONARKIT_ENABLED  and commented pluggins work in RN 0.67+ versions
    // #if FB_SONARKIT_ENABLED
    let client = FlipperClient.shared()
    let layoutDescriptorMapper = SKDescriptorMapper(defaults: ())
    // FlipperKitLayoutComponentKitSupport.setUpWith(layoutDescriptorMapper)
    client?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))
    client?.add(FKUserDefaultsPlugin(suiteName: nil))
    client?.add(FlipperKitReactPlugin())
    client?.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter()))
    // client?.add(FlipperReactPerformancePlugin.sharedInstance())
    client?.start()
    // #endif
    #endif
  }

  func sourceURL(for bridge: RCTBridge!) -> URL! {
    #if DEBUG
      return RCTBundleURLProvider.sharedSettings()?.jsBundleURL(forBundleRoot: "index", fallbackResource: nil)
    #else
      return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
    #endif
  }

  // var window: UIWindow? // >>> window created in Base class
  var bridge: RCTBridge!
  var orientationLock: UIInterfaceOrientationMask = .portrait
  var rootView: RCTRootView?
  var isScreenRecordingEnabled: Bool?
  var isCaptured: Bool?
  public weak var authorizationFlowManagerDelegate: RNAppAuthAuthorizationFlowManagerDelegate?

  func application(_ application: UIApplication,
                   willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
   
    _ = localizedString(LocalizationKey(key: "adyen.card.expiryItem.title"), nil)

    //  Start screen recording in a disabled state
    isScreenRecordingEnabled = false
    let config = ReactNativeConfig.env()!
    WonderPush.setClientId((config["WONDERPUSH_CLIENT_ID"] as? String)!,
                           secret: (config["WONDERPUSH_CLIENT_SECRET"] as? String)!)
    WonderPush.setupDelegateForUserNotificationCenter()
    WonderPush.setRequiresUserConsent(false)
    WonderPush.setUserConsent(true)

    return true
  }
  // swiftlint:disable block_based_kvo
  // swiftlint:disable colon
  override func observeValue(forKeyPath keyPath: String?, of object: Any?,
                             change: [NSKeyValueChangeKey : Any]?,
                             context: UnsafeMutableRawPointer?) {
    if #available(iOS 11.0, *) {
      if keyPath == "captured" {
        isCaptured = UIScreen.main.isCaptured
        if !isScreenRecordingEnabled! {
          if isCaptured! {
            rootView?.isHidden = true
          }
          if !isCaptured! && rootView?.isHidden == true {
            rootView?.isHidden = false
          }
        }
      }
    }
  }

  override func application(_ application: UIApplication,
                            didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // >>> important to call super.application(_, didFinishLaunchingWithOptions) that is in AppDelegateBase **
    super.application(application, didFinishLaunchingWithOptions: launchOptions)
    // swiftlint:disable force_cast
    launchDidomi(apiKey: ReactNativeConfig.env()!["DIDOMI_API_KEY"] as! String)
    FirebaseApp.configure()
    initializeFlipper(with: application)

    let receiverAppID = kGCKDefaultMediaReceiverApplicationID // or "ABCD1234"
    let criteria = GCKDiscoveryCriteria(applicationID: receiverAppID)
    let options = GCKCastOptions(discoveryCriteria: criteria)
    GCKCastContext.setSharedInstanceWith(options)
    GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

    self.bridge = RCTBridge(delegate: self, launchOptions: launchOptions)
    guard let bridge = self.bridge else {
      return false
    }
    // >>> props created from AppDelegateBase
    var props: [AnyHashable: Any] = prepareInitialProps()
    props["APP_INFO"] = (Bundle.main.infoDictionary?["APP_INFO"] as? String) ?? ""
    props["VERSION"]: (Bundle.main.infoDictionary?["CFBundleShortVersionString"]) ?? ""

    rootView = RCTRootView(bridge: bridge, moduleName: "PSG", initialProperties: props)

    rootView?.backgroundColor = UIColor(red: 22.0/255.0, green: 33.0/255.0, blue: 45.0/255.0, alpha: 1.0)
    
    // >>> self.window is created in AppDelegateBase
    let rootViewController = UIViewController()
    rootViewController.view = rootView
    self.window?.rootViewController = rootViewController
    self.window?.makeKeyAndVisible()

    RNSplashScreen.show()

    WonderPush.application(application, didFinishLaunchingWithOptions: launchOptions)
    UIScreen.main.addObserver(self, forKeyPath: "captured", options: .new, context: nil)

    
    NotificationCenter.default.addObserver(self, selector: #selector(self.disableScreenRecording(notification:)), name: Notification.Name("DisableScreenRecording"), object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(self.enableScreenRecording(notification:)), name: Notification.Name("EnableScreenRecording"), object: nil)
    return true
  }

  private func launchDidomi(apiKey: String) {
    Didomi.shared.setLogLevel(minLevel: OSLogType.info.rawValue)

    Didomi.shared.initialize(
      apiKey: apiKey,
      localConfigurationPath: nil,
      remoteConfigurationURL: nil,
      providerId: nil,
      disableDidomiRemoteConfig: false
    )
  }

  @objc func disableScreenRecording(notification: NSNotification) {
    isScreenRecordingEnabled = false
    DispatchQueue.main.async {
      if self.isCaptured ?? false {
        self.rootView?.isHidden = true
      }
    }
  }
  @objc func enableScreenRecording(notification: NSNotification) {
    isScreenRecordingEnabled = true
  }
  func application(
      _ application: UIApplication,
      continue userActivity: NSUserActivity,
      restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
  ) -> Bool {
    // https://firebase.google.com/docs/dynamic-links/ios/receive#open-dynamic-links-in-your-app step 6
    let handled = DynamicLinks.dynamicLinks()
      .handleUniversalLink(userActivity.webpageURL!) { dynamiclink, error in
        _ = dynamiclink
        _ = error
        // no-op
      }

    return handled || RCTLinkingManager.application(
        application,
        continue: userActivity,
        restorationHandler: restorationHandler)
  }
  
  func application(_ app: UIApplication,
                   open url: URL,
                   options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    // https://firebase.google.com/docs/dynamic-links/ios/receive#open-dynamic-links-in-your-app step 7
    if DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) != nil {
     
      return true
    }

    RCTLinkingManager.application(app, open: url, options: options)
    if ApplicationDelegate.shared.application(app, open: url, options: options) {
      return true
    }
    // swiftlint:enable colon
    if Adyen.RedirectComponent.applicationDidOpen(from: url) { return true }

    return authorizationFlowManagerDelegate?.resumeExternalUserAgentFlow(with: url) ?? false
  }

  // swiftlint:disable colon
  func application(_ application: UIApplication,
                   didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
    // swiftlint:enable colon
    WonderPush.application(application,
                           didReceiveRemoteNotification: userInfo)
  }

  func application(_ application: UIApplication,
                   didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    WonderPush.application(application,
                           didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
  }

  func application(_ application: UIApplication,
                   didFailToRegisterForRemoteNotificationsWithError error: Error) {
    WonderPush.application(application,
                           didFailToRegisterForRemoteNotificationsWithError: error)
  }

  // swiftlint:disable colon
  func application(_ application: UIApplication,
                   didReceiveRemoteNotification userInfo: [AnyHashable : Any],
                   fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    // swiftlint:enable colon
    WonderPush.application(application,
                           didReceiveRemoteNotification: userInfo,
                           fetchCompletionHandler: completionHandler)
  }

  func applicationDidBecomeActive(_ application: UIApplication) {
    WonderPush.applicationDidBecomeActive(application)
  }

  func applicationDidEnterBackground(_ application: UIApplication) {
    WonderPush.applicationDidEnterBackground(application)
  }

  func application(_ application: UIApplication,
                   supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return orientationLock
  }
}

This should automatically create a Bridging header for your project. Add this to your bridging header:

#include "AppDelegateBase.h"
like image 123
mani Avatar answered Oct 17 '25 12:10

mani



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!