I am new to ReactNative. In my current app, deeplinking was coded and working well when it was done through appDelegate. later, the app support Carplay as well, so to handle multiple scenes sceneDelegate was introduced.
After that, whenever deeplinking is invoked then
What is the issue here, what things I need to change / add to make app launch at desired location even if app is removed from memory.
Here is my appDelegate.
func appDelegate() -> AppDelegate {
return UIApplication.shared.delegate as! AppDelegate
}
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var appCenter: AppCenterReactNative!
var appCenterAnaltics: AppCenterReactNativeAnalytics!
var appCenterCrashes: AppCenterReactNativeCrashes!
let mParticleKey: String = ReactNativeConfig.env(for: "MPARTICLE_IOS_KEY");
let mParticleSecret: String = ReactNativeConfig.env(for: "MPARTICLE_IOS_SECRET");
// var mParticleEmail: String = ReactNativeConfig.env(for: "MPARTICLE_EMAIL");
let mParticleEnv: String = ReactNativeConfig.env(for: "MPARTICLE_ENV");
let mParticleDataPlanName: String = ReactNativeConfig.env(for: "MPARTICLE_DATAPLAN");
let mParticleDataPlanVersion: String = ReactNativeConfig.env(for: "MPARTICLE_DATAPLAN_VERSION");
let moEngageAppID: String = ReactNativeConfig.env(for: "MOENGAGE_APP_ID");
/* CarPlay setup */
var playableContentManager: MPPlayableContentManager?
var remoteCommandCenter: MPRemoteCommandCenter?
let carplayPlaylist = CarPlayPlaylist()
let carplayArtworkCache = NSCache<AnyObject, UIImage>()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
initializeFlipper(with: application)
/* Moengage */
let sdkConfig = MoEngageSDKConfig(appId: moEngageAppID, dataCenter: .data_center_01);
MoEngageInitializer.sharedInstance().initializeDefaultSDKConfig(sdkConfig, andLaunchOptions: launchOptions ?? [:])
AppCenterReactNative.register()
AppCenterReactNativeAnalytics.register(withInitiallyEnabled: true);
AppCenterReactNativeCrashes.registerWithAutomaticProcessing();
FirebaseApp.configure()
/* ChromeCast activate */
let receiverAppID:String = "CC1AD845"; // or @"ABCD1234"
let criteria = GCKDiscoveryCriteria(applicationID: receiverAppID)
let options = GCKCastOptions(discoveryCriteria: criteria)
GCKCastContext.setSharedInstanceWith(options)
let bridge = RCTBridge(delegate: self, launchOptions: launchOptions)!
let rootView = RCTRootView(bridge: bridge, moduleName: "nova", initialProperties: nil)
let rootViewController = UIViewController()
rootViewController.view = rootView
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = rootViewController
self.window?.makeKeyAndVisible()
/* MPNowPlayingInfoCenter */
UIApplication.shared.beginReceivingRemoteControlEvents()
setupCarPlay();
RNSplashScreen.show()
/* Setup MParticle */
var dPlanVersion:NSNumber = 0
if let versionInt = Int(mParticleDataPlanVersion) {
dPlanVersion = NSNumber(value:versionInt)
}
var mParticleEnvMode: MPEnvironment = MPEnvironment.development
if(mParticleEnv == "PROD") {
mParticleEnvMode = MPEnvironment.production
}
let mParticleOptions = MParticleOptions(key: mParticleKey, secret: mParticleSecret)
mParticleOptions.environment = mParticleEnvMode
mParticleOptions.dataPlanId = mParticleDataPlanName
mParticleOptions.dataPlanVersion = dPlanVersion
mParticleOptions.proxyAppDelegate = false
if #available(iOS 14, *) {
mParticleOptions.attStatus = NSNumber.init(value: ATTrackingManager.trackingAuthorizationStatus.rawValue)
}
// Remove AST Events
mParticleOptions.onCreateBatch = { (batch: [AnyHashable: Any]) -> [AnyHashable: Any]? in
var modifiedBatch = batch
guard var modifiedMessages = batch["msgs"] as? [AnyHashable] else { return batch }
var index = 0
for message in modifiedMessages {
// the following removes Application State Transition (AST) events, except for those uploaded on installs and upgrades
// Install AST events are used by many server-side integrations and are used by
// mParticle to ensure there is a user profile created
guard let messageAsDictionary = message as? [AnyHashable: Any] else { continue }
guard let type = messageAsDictionary["dt"] as? String else { continue }
let isFirstRun = messageAsDictionary["ifr"] as? Bool ?? false
let isUpgrade = messageAsDictionary["iu"] as? Bool ?? false
if type == "ast" && !isFirstRun && !isUpgrade {
modifiedMessages.remove(at: index)
index -= 1
}
index += 1
}
modifiedBatch["msgs"] = modifiedMessages
return modifiedBatch
}
// Start the SDK
MParticle.sharedInstance().start(with: mParticleOptions)
return true
}
private func initializeFlipper(with application: UIApplication) {
#if DEBUG
let client = FlipperClient.shared()
let layoutDescriptorMapper = SKDescriptorMapper(defaults: ())
client?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))
client?.add(FKUserDefaultsPlugin(suiteName: "nova"))
client?.add(FlipperKitReactPlugin())
client?.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter()))
client?.start()
#endif
}
/* Allow for orientation change */
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return Orientation.getOrientation()
}
/* Allow Link back URLs ('nova://') */
public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
return RCTLinkingManager.application(app, open: url, options: options)
}
public func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
return RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
}
extension AppDelegate: RCTBridgeDelegate {
func sourceURL(for bridge: RCTBridge!) -> URL! {
#if DEBUG
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
return CodePush.bundleURL()
#endif
}
}
This is my sceneDelegate
@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate, RCTBridgeDelegate {
let mParticleKey: String = ReactNativeConfig.env(for: "MPARTICLE_IOS_KEY");
let mParticleSecret: String = ReactNativeConfig.env(for: "MPARTICLE_IOS_SECRET");
// var mParticleEmail: String = ReactNativeConfig.env(for: "MPARTICLE_EMAIL");
let mParticleEnv: String = ReactNativeConfig.env(for: "MPARTICLE_ENV");
let mParticleDataPlanName: String = ReactNativeConfig.env(for: "MPARTICLE_DATAPLAN");
let mParticleDataPlanVersion: String = ReactNativeConfig.env(for: "MPARTICLE_DATAPLAN_VERSION");
func sourceURL(for bridge: RCTBridge!) -> URL! {
let jsCodeLocation: URL
jsCodeLocation = RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
return jsCodeLocation
}
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
var deeplink: URL?
if let userActivity = connectionOptions.userActivities.first(where: { $0.activityType == NSUserActivityTypeBrowsingWeb }),
let webpageURL = userActivity.webpageURL {
// get universal link
deeplink = webpageURL
} else if let urlContext = connectionOptions.urlContexts.first {
// get app scheme deep link
deeplink = urlContext.url
}
handleDeepLink(deeplink)
let bridge = RCTBridge.init(delegate: self, launchOptions: nil)
let rootView = RCTRootView.init(bridge: bridge!, moduleName: "nova", initialProperties: nil)
let rootViewController = UIViewController()
rootViewController.view = rootView
AppCenterReactNative.register()
AppCenterReactNativeAnalytics.register(withInitiallyEnabled: true);
AppCenterReactNativeCrashes.registerWithAutomaticProcessing();
FirebaseApp.configure()
/* ChromeCast activate */
let receiverAppID:String = "CC1AD845"; // or @"ABCD1234"
let criteria = GCKDiscoveryCriteria(applicationID: receiverAppID)
let options = GCKCastOptions(discoveryCriteria: criteria)
GCKCastContext.setSharedInstanceWith(options)
/* MPNowPlayingInfoCenter */
UIApplication.shared.beginReceivingRemoteControlEvents()
RNSplashScreen.show()
// Instantiate root view here instead of scene to start the bundler on app launch
RNBridgeInstanceHolder.sharedInstance.bridge = bridge
RNBridgeInstanceHolder.sharedInstance.rctRootView = rootView
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = rootViewController
self.window = window
window.makeKeyAndVisible()
}
if #unavailable(iOS 14.0) {
appDelegate().setupCarPlay()
}
/* Setup MParticle */
var dPlanVersion:NSNumber = 0
if let versionInt = Int(mParticleDataPlanVersion) {
dPlanVersion = NSNumber(value:versionInt)
}
var mParticleEnvMode: MPEnvironment = MPEnvironment.development
if(mParticleEnv == "PROD") {
mParticleEnvMode = MPEnvironment.production
}
let mParticleOptions = MParticleOptions(key: mParticleKey, secret: mParticleSecret)
mParticleOptions.environment = mParticleEnvMode
mParticleOptions.dataPlanId = mParticleDataPlanName
mParticleOptions.dataPlanVersion = dPlanVersion
mParticleOptions.proxyAppDelegate = false
if #available(iOS 14, *) {
mParticleOptions.attStatus = NSNumber.init(value: ATTrackingManager.trackingAuthorizationStatus.rawValue)
}
// Remove AST Events
mParticleOptions.onCreateBatch = { (batch: [AnyHashable: Any]) -> [AnyHashable: Any]? in
var modifiedBatch = batch
guard var modifiedMessages = batch["msgs"] as? [AnyHashable] else { return batch }
var index = 0
for message in modifiedMessages {
// the following removes Application State Transition (AST) events, except for those uploaded on installs and upgrades
// Install AST events are used by many server-side integrations and are used by
// mParticle to ensure there is a user profile created
guard let messageAsDictionary = message as? [AnyHashable: Any] else { continue }
guard let type = messageAsDictionary["dt"] as? String else { continue }
let isFirstRun = messageAsDictionary["ifr"] as? Bool ?? false
let isUpgrade = messageAsDictionary["iu"] as? Bool ?? false
if type == "ast" && !isFirstRun && !isUpgrade {
modifiedMessages.remove(at: index)
index -= 1
}
index += 1
}
modifiedBatch["msgs"] = modifiedMessages
return modifiedBatch
}
// Start the SDK
MParticle.sharedInstance().start(with: mParticleOptions)
}
//handels app scheme novaplayer:// in active and inactive foreground mode
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url {
handleDeepLink(url)
}
}
//handels universal links https://novaplayer in active and inactive foreground mode
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
if let url = userActivity.webpageURL {
handleDeepLink(url)
}
}
}
//function to pass deelink value to react native
func handleDeepLink(_ deeplink: URL?) {
guard let deeplink = deeplink else {
os_log("No deeplink found", log: OSLog.default, type: .debug)
return
}
os_log("Deeplink URL FOUND: %@", log: OSLog.default, type: .debug, deeplink.absoluteString)
RCTLinkingManager.application(UIApplication.shared, open: deeplink, options: [:])
}
}
Here is my info.plist piece of code.
<key>UIApplicationSceneManifest</key>
<dict>
<key>UISceneConfigurations</key>
<dict>
<key>CPTemplateApplicationSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>CPTemplateApplicationScene</string>
<key>UISceneConfigurationName</key>
<string>CarPlay Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).CarPlaySceneDelegate</string>
</dict>
</array>
<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>
It is working as expected in Android build, but having issue only with iOS build. If I switch back to appDelegate without scenedelegate then my Carplay app is not getting launched.
Any help will be appreciated.
Thanks.
I sort it out with the help of @sonle
by adding time to execute the deeplink, sorted the issue.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
handleDeepLink(deeplink)
}
Thanks for guidance Sonle.