react-native-sdk
react-native-sdk copied to clipboard
Deep links are not working when app is closed
Hello!
I configured the deep links as explained on the docs and the deep links are working fine when the app is opened, in background, and you tap over a deep link.
But when the app is closed, the urlHandler is not fired, so the links are not opened.
I've only tested in IOS so far.
This is my AppDelegate.me:
#import "AppDelegate.h"
#import "BraintreeCore.h"
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <GoogleMaps/GoogleMaps.h>
#import <FBSDKCoreKit/FBSDKCoreKit.h>
#import <CommonUISDK/CommonUISDK.h>
#import <UserNotifications/UserNotifications.h>
#import <React/RCTLinkingManager.h>
#import <Firebase.h>
@import IterableSDK;
#if DEBUG
#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
static void InitializeFlipper(UIApplication *application) {
FlipperClient *client = [FlipperClient sharedClient];
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
[client addPlugin:[FlipperKitReactPlugin new]];
[client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
[client start];
}
#endif
#endif
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[GMSServices provideAPIKey:@"string/GOOGLE_MAPS_KEY"];
if ([FIRApp defaultApp] == nil) {
[FIRApp configure];
}
#if DEBUG
#ifdef FB_SONARKIT_ENABLED
InitializeFlipper(application);
#endif
#endif
[self setupNotifications];
[ZDKCommonTheme currentTheme].primaryColor = [UIColor colorWithRed: 0.25 green: 0.15 blue: 0.23 alpha: 1.00];
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"Test"
initialProperties:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
[BTAppSwitch setReturnURLScheme:self.paymentsURLScheme];
return YES;
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
- (NSString *)paymentsURLScheme {
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
return [NSString stringWithFormat:@"%@.%@", bundleIdentifier, @"payments"];
}
#pragma mark - UNUserNotificationCenterDelegate
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
completionHandler(UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound);
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
[IterableAppIntegration userNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:completionHandler];
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[IterableAPI registerToken:deviceToken];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[IterableAppIntegration application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}
#pragma mark — Handling URLs
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
if ([url.scheme localizedCaseInsensitiveCompare:self.paymentsURLScheme] == NSOrderedSame) {
return [BTAppSwitch handleOpenURL:url options:options];
}
if ([[url.scheme substringToIndex: 2] isEqualToString:@"fb"]) {
return [[FBSDKApplicationDelegate sharedInstance]application:application
openURL:url
options:options];
}
BOOL handled = [RCTLinkingManager application:application openURL:url options:options] || [[FBSDKApplicationDelegate sharedInstance] application:application
openURL:url
sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
annotation:options[UIApplicationOpenURLOptionsAnnotationKey]
];
return handled;
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
return [IterableAPI handleUniversalLink:userActivity.webpageURL] || [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
#pragma mark - private
- (void) setupNotifications {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
}
@end
This is my configuration on the JS side. It is executed on the App.js:
const config = new IterableConfig();
config.checkForDeferredDeepLink = true;
config.inAppHandler = () => {
return IterableInAppShowResponse.show;
};
config.urlHandler = url => {
if (isUrlHandledByApp(url)) {
Linking.openURL(changeUrlToCustomScheme(url));
return true;
}
return false;
};
Iterable.initialize(Config.ITERABLE_API_KEY, config);
Please help.
Thanks in advance!
We are seeing this with
"@iterable/react-native-sdk": "1.0.26"
"react-native": "0.64.1",
I also tried version 1.0.28, on both iOS and Android. Never got a url to open after push from a terminated app. Worked for both backgrounded and foregrounded.
We also tested on 1.1.0-beta2. The IOS build failed, Android built but the same issue occured.
I should mention our urlHandler was designed to just let the OS handle our custom schema links, like this:
iterableUrlHandler = (url: string, context: IterableActionContext) => {
return false;
};
TL/DR: We found and fixed the issue, it was particular to our project setup. Iterable push deep links are working correctly on @iterable/react-native-sdk: "1.0.28" with both OS's in all conditions (terminated, backgrounded, foregrounded).
I'm not completely sure if this is something that should be tweaked for this SDK, or given a note in the docs, or even belongs as an issue here at all. I just want to share what we found.
Long version.
-
Background on our project
- We're using
react-navigation v6, using their built-in linking to route based on incoming links - We used ignite cli to scaffold our current app
- They structure it so you setup your services when you create the root store.
- Before the root nav is created (and all services have completed setup), none of the UI is rendered. There are trade offs to this, but it avoids race conditions of seeing flashes of temporary frames, or wasting CPU on renders that aren't needed on startup.
- It is also built with dependency injection, rather than singleton module imports.
- We had created an
IterableServicethat relies on a DI injectedconfigServiceto init the Iterable SDK as part of the overallawait envrionment.setup()
- We're using
-
What the probem was
- When
Iterable.initialize()is called, the SDK does a number of things, one of which appears to be handling any deep links which were sent via Iterable pushes and callingLinking.openUrl(). - Our problem was that
Iterable.initialize()was called before ourreact-navigationRootNavigatorwas mounted, meaning we didn't have a listener yet set up to capture theLinking.openUrl()thatIterable.initialize()sent over. - Normally this isn't an issue for a terminated app getting a deep link, since the linking libs always make a single call to
getInitialURL()to see if a link request was sent before the listener was registered. But sinceIterable.initialize()is called after the app is open, the URL it sends doesn't get intogetInitialUrl(), and only sent to current listeners (which need to exist already to capture the events).
- When
-
Our fix
- I thought about quite a few different wants to fix this, but I settled on not delaying the mount of
RootNavigatoruntil all the services are.setup. There are a number of impacts this has to initial app launch which I won't get into here, but a side effect being that our linking url listener is sure to be active before weIterable.initialize()
- I thought about quite a few different wants to fix this, but I settled on not delaying the mount of
app.tsx
React.useEffect(() => {
if (dependencies) {
dependencies.setup();
}
}, [dependencies]);
@harrydema Short version of above post.
- For testing, throw a 5 second
setTimeoutaround yourIterable.initialize(Config.ITERABLE_API_KEY, config);. - If that fixes the deep link issue for you, then you have the same issue that we had. You need to make sure your
RootNavigatoris mounted before callingIterable.initialize. Remove thesetTimeoutand refactor to make it work.
We had a similar issue like @jehartzog so this was what solved our issue
Platform: IOS (Tested it only on IOS)
- Upgraded to the RN Iterable SDK newest version 1.1.3
- Ensure our React Native Root Navigator was mounted before the SDK initialization. As @jehartzog suggested above.
- Ensure to have ONLY one RN Iterable SDK instance initialized. (We had an extra call to the SDK, this was the root cause once we have ensured the Root Navigator was mounted before the SDK call) @jehartzog thanks Hopefully, this help someone here.