Deep Link is not handled when app is closed React Native iOS app
Asked Answered
E

2

7

I'm developing a React Native app for iOS.

This app has to be able to open deeplinks and it works fine when the app is opened in background. When the app is close, the native iOS code (Objective-C) does not get the URL the app has been opened with.

As I understand how it works, I have to check the NSDictionary of the launchOptions to see if the app has been started through an URL. If the key corresponding to URL initialization exists then I return true to execute the following code

- (BOOL)application:(UIApplication *)application
        openURL:(NSURL *)url
        options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
  return [RCTLinkingManager application:application openURL:url options:options];

  return YES;
}

This is the function that has to bee executed to get the Initial Url which the app has been opened with. Here is my code of app didFinishWithLaunchOptions:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary  *)launchOptions
{
    if ([launchOptions objectForKey:UIApplicationLaunchOptionsURLKey]) {
    return true;
  }
}
Enrichment answered 27/3, 2019 at 19:12 Comment(1)
Were you able to solve this or not as we are also stuck in the same position in IOS only Please let me know if you find the solution? ThanksPeggi
N
2

The core issue of Intercom not handling deep links for me was that react-native-intercom does not pass an initial URL to an application through didFinishLaunchingWithOptions if it's opened from the background.

Instead react-native-intercom calls the openURL method immediately after the application is launched and the react-native part of the application misses this event.

The solution for this was to create a new thread in the openURL method. Lock it to stop the execution and then unlock the tread when React application gives a signal to the native code, that it's ready to handle deep link. The fix is slightly complicated, so I've created a separate repository, describing the problem and the solution.

The core idea can be described in code as:

dispatch_queue_t queue = dispatch_queue_create("<yourAppName>.openUrlQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
  while (!DeepLink.canHandleDeepLinks) {
    [DeepLink.canHandleDeepLinksLock wait];
  }
  [DeepLink.canHandleDeepLinksLock unlock];


  dispatch_async(dispatch_get_main_queue(), ^{
    // This method call will trigger the Linking event with URL to be dispatched to your 'javascript' code
    [RCTLinkingManager application:application openURL:url options:options];
  });
});

I've created a repository with the solution to this problem. Go, check it out.

Napper answered 2/9, 2020 at 13:30 Comment(0)
B
1

While definitely not an answer, I've (terribly) worked around this issue (at least until a canonical solution is available) by changing (or if you don't already have, adding) the - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler { in AppDelegate.m to:

- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
 restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
  NSURL *url = [userActivity webpageURL];
  BOOL result = [RCTLinkingManager application:application
                          continueUserActivity:userActivity
                            restorationHandler:restorationHandler];
  if([userActivity webpageURL]){
    #if DEBUG
    float seconds = 3.5;
    #else
    float seconds = 1.5;
    #endif
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(seconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      NSURL *newUrl = [NSURL URLWithString:[NSString stringWithFormat:@"MYAPPSCHEME:/%@", url.path]];
      [[UIApplication sharedApplication] openURL:newUrl options:@{} completionHandler:nil];
    });
  }
  
  return result;
}

It basically checks if there's a URL available that caused the launch, and invokes an open URL call to the app after "things have settled down" (as in debug mode it takes longer to load, I've changed to 3.5 seconds which handles it just well). Change the seconds and of course MYAPPSCHEME accordingly. And YES, there is a single slash (/) after MYAPPSCHEME: instead of double as url.path seems to have a leading slash already. The reason for replacing http[s] with my app scheme is that for some reason it launches Safari instead of handling deep link if I leave http[s]:// (which is not the case when app is already running and an URL is handled). My app handles custom schemes the same way with regular http links so it works well, but make sure you set it up accordingly for it to work.

After making the above changes and recompiling (don't forget that you're at Objective-C part, not JS/React Native) it worked. I'd love to see an actual solution rather than a hacky workaround, but until then, this fixed it for me.

Bannockburn answered 14/8, 2020 at 11:42 Comment(1)
nice workaround, it worked for me! for future readers, MYAPPSCHEME must be replaced with the one in XCode -> Targets [your app] -> Info -> URL Types -> URL SchemesTepper

© 2022 - 2024 — McMap. All rights reserved.