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?
2
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
:Add another new file:
AppDelegateBase.mm
– (Based on AppDelegate.mm from react diff project)Update your
AppDelegate.swift
.. Have marked the changes with>>>
.This should automatically create a Bridging header for your project.
Add this to your bridging header:
As per react native doc https://reactnative.dev/docs/new-architecture-app-intro
TurboModules can be written using Objective-C or C++. In order to support both cases, any source files that include C++ code should use the .mm file extension. This extension corresponds to Objective-C++, a language variant that allows for the use of a combination of C++ and Objective-C in source files.
Read all doc related to iOS carefully
AND
Implement this method in swift
Make sure to mark above method as @objc method