React-native: iOS Linking getInitialURL() always null

Created on 12 Apr 2019  ยท  56Comments  ยท  Source: facebook/react-native

๐Ÿ› Bug Report

I can't get the deep link url that was called when my app is started on iOS.
Tried with Linking.getInitialUrl() promise and by listener addEventListener('url'...)
None of these methods return the initial url.

When the app is in background, the listener addEventListener('url', ...) is working well, and we can get the url. But when the app is launched via a deeplink, we don't have it.

I tried on React Native 0.53.3 it's working via getInitialUrl() and listener.
I tried on React Native 0.59.4 not working.

To Reproduce

1. Add URL type to info.plist.

2. Add this to your AppDelegate.m :

#import <React/RCTLinkingManager.h>
...
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
  return [RCTLinkingManager application:application openURL:url
                      sourceApplication:sourceApplication annotation:annotation];
}

3. and just to test, update your App.js to look like this :

import React, { Component } from 'react';
import {
  Platform,
  StyleSheet,
  Text,
  View,
  Linking
} from 'react-native';

type Props = {};
export default class App extends Component<Props> {
  componentDidMount() {
    Linking.getInitialURL().then((url) => { console.log('1', url) })
    Linking.addEventListener('url', this.handleOpenURL);
  }
  componentWillUnmount() {
    Linking.removeEventListener('url', this.handleOpenURL);
  }
  handleOpenURL(event) {
    console.log('2', event.url);
  }

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Welcome to React Native!
        </Text>
      </View>
    );
  }
}

Expected Behavior

I expect to get the initial URL called via deeplink via getInitialUrl or listener handler.

Environment

info
  React Native Environment Info:
    System:
      OS: macOS 10.14.4
      CPU: (8) x64 Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz
      Memory: 205.05 MB / 16.00 GB
      Shell: 5.3 - /bin/zsh
    Binaries:
      Node: 11.11.0 - /usr/local/bin/node
      Yarn: 1.15.2 - /usr/local/bin/yarn
      npm: 6.7.0 - /usr/local/bin/npm
      Watchman: 4.9.0 - /usr/local/bin/watchman
    SDKs:
      iOS SDK:
        Platforms: iOS 12.2, macOS 10.14, tvOS 12.2, watchOS 5.2
      Android SDK:
        API Levels: 19, 23, 24, 25, 26, 27, 28
        Build Tools: 23.0.1, 24.0.1, 25.0.0, 25.0.2, 25.0.3, 26.0.1, 26.0.2, 26.0.3, 27.0.3, 28.0.3
        System Images: android-19 | Google APIs ARM EABI v7a, android-19 | Google APIs Intel x86 Atom, android-23 | Google APIs Intel x86 Atom, android-25 | Google Play Intel x86 Atom, android-27 | Google APIs Intel x86 Atom
    IDEs:
      Android Studio: 3.3 AI-182.5107.16.33.5264788
      Xcode: 10.2/10E125 - /usr/bin/xcodebuild
    npmPackages:
      react: 16.8.3 => 16.8.3
      react-native: 0.59.4 => 0.59.4
    npmGlobalPackages:
      create-react-native-app: 1.0.0
      react-native-git-upgrade: 0.2.7

Thanks for your time ;)

Linking Bug iOS

Most helpful comment

Hi,
In my project with react-navigation 3.9.1 and react-native 0.59.5 deep linking don't work on ios simulator when "Debug JS remotely" is enabled, but work well when the debug is disabled.
Could you try getInitialURL() without debug mode ?

All 56 comments

same here
https://github.com/dmitri-wm/deep-linking-sample sample fresh installed app with deep linking setup for IOS
@matthieupinte did you find workaround?

React native 0.58.5 works fine

@dmitri-wm No, I don't have more information... and we can't go back to 0.58.5 because of Android requirements.

Android has supported 64-bit CPUs since 5.0 Lollipop, and the Play Store in 2017 announced that apps using native code must provide a 64-bit version in light of future chips that only support 64-bit code.

And I heard somewhere that only RN 0.59.x has support for Android 64-bit...

On Android getInitialURL() works as expected, the issue is on iOS, url always comes up null.
react-native 0.59.x

I just encountered the same issue. getInitialURL was always null on iOS with react-native 0.59.x. I downgraded to 0.58.5 and it works as expected.

Have the same problem on RN 0.59.

npmPackages:
  react: 16.8.3 => 16.8.3 
  react-native: 0.59.4 => 0.59.4 

Hi,
In my project with react-navigation 3.9.1 and react-native 0.59.5 deep linking don't work on ios simulator when "Debug JS remotely" is enabled, but work well when the debug is disabled.
Could you try getInitialURL() without debug mode ?

I confirm getInitialURL() is always null when Debug JS remotely is enabled ( I use React Native debugger 0.9.7)

Hi,
I have same problem in IOS. (Working only when remote debugging is disabled)

  react: 16.8.3 
  react-native: 0.59.5 (and 0.59.8)

Xcode:

- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {

  BOOL handledFB = [[FBSDKApplicationDelegate sharedInstance] application:application
                                                                openURL:url
                                                      sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
                                                             annotation:options[UIApplicationOpenURLOptionsAnnotationKey]
                  ];

  BOOL handleCustom = [RCTLinkingManager application:application openURL:url options:options];

  return handledFB || handleCustom;
}

I can second this. Working on Android, not iOS

"react": "16.8.3",
"react-native": "0.59.3",

Edit: Can also confirm that its only when remote debugging is enabled.

Hi,
In my project with react-navigation 3.9.1 and react-native 0.59.5 deep linking don't work on ios simulator when "Debug JS remotely" is enabled, but work well when the debug is disabled.
Could you try getInitialURL() without debug mode ?

I also confirm : Linking.getInitialURL() will return your url once Debug JS Remotely is disabled

Confirm Linking.getInitialURL() and Linking.addEventListener('url', () => {}) not work

  • iOS
  • 0.59.5
  • Simulator
  • Universal Link

UPDATE: my mistake. I solved it by adding this method to AppDelegate.m

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
  return [RCTLinkingManager application:application
                   continueUserActivity:userActivity
                     restorationHandler:restorationHandler];
}

Confirmed Linking.getInitialURL() always returns null when remote debug is enabled.
iOS
0.59.8
Simulator
Universal Link

Strange enough. Android simulator, android real device works. Real iPhone works. Simulator only works when debugging disabled.

Info
React Native Environment Info:
System:
OS: macOS 10.14.4
CPU: (8) x64 Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
Memory: 74.79 MB / 16.00 GB
Shell: 5.3 - /bin/zsh
Binaries:
Node: 10.10.0 - /usr/local/bin/node
Yarn: 1.15.2 - /usr/local/bin/yarn
npm: 6.7.0 - /usr/local/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
SDKs:
iOS SDK:
Platforms: iOS 12.2, macOS 10.14, tvOS 12.2, watchOS 5.
IDEs:
Android Studio: 3.1 AI-173.4720617
Xcode: 10.2.1/10E1001 - /usr/bin/xcodebuild
npmPackages:
react: 16.8.3 => 16.8.3
react-native: 0.59.5 => 0.59.5
npmGlobalPackages:
react-native-cli: 2.0.1
react-native-git-upgrade: 0.2.7

I can also confirm it's not working on iOS when remote debugging is enabled.

"react": "16.8.3",
"react-native": "0.59.3",

I checked in my application using [email protected] and Xcode 10.2.1.

It works. Try it:

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

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
  return [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
}

Update: I see what problem exist only when Debug JS Remotely enabled.
Suggestion: Use Reactotron for debug this.
Also I think it can be related with this commit.

It also happens when Live Reload is active or with any instance that refreshes bundled JS. To test is needed to be disconnected from debug and reload [email protected] && Xcode 10.2.1

Same problem here

can you try ?

getInitialUrl = async () => { const url = await Linking.getInitialURL() return url }

Yeah, pretty annoying issue, does anybody tracked commit, that introduced this regression?

@IljaDaderko I see You fixed universal links recently, maybe some ideas on where it has been introduced?

@todorone hard to tell. My change didn't touch that area.

For iOS native code is defined here
https://github.com/facebook/react-native/blob/master/Libraries/LinkingIOS/RCTLinkingManager.m#L147-L161

And JavaScript implementation is here
https://github.com/facebook/react-native/blob/master/Libraries/Linking/Linking.js#L86-L92

Judging by what I can see, these are being worked on right now, there was also change to use TurboModules, perhaps that affected it somehow?

Same here, also getting null back from getInitialURL on iOS.

Edit: Seems to work but only when debug/live reload is not active.

Any updates on this issue?
Can confirm that is not working on react-native: 0.59.10, iOS + debugging on
As a workaround, turn off debugging and use Reactotron or maybe Alert dialog to show urls.

having the same issue with RN 60.4, works if turn off remote debugger

on 0.59.10, tried without the remote debugger, no live reload.
Unfortunately, Linking.getInitialURL() is not working for me.

Linking.addListener is working fine... but I need Linking.getInitialURL...

getInitialUrl() does not work on both ios & android even if I turn off remote debugging mode:

[email protected]
[email protected](10E125)

Same here. Not working on Anroid with react-native: 0.61.2

Sames goes for me, does not work without debug mode

getInitialURL is null on iOS even when not remote debugging or live loading when the app is returning from a dead state opening a deeplink. Hence deeplinks from dead state are broken in the wild.

application openURL gets the correct url in Xcode.

"react-native": "0.59.9",
"react": "16.8.3",

Xcode 11.1.

I thought we were having this same issue until I realized it was because our RCTBridge was being torn down and re-initialized between the application launching and getInitialURL being called.

If you look at how RCTLinking works, it fetches the initialURL from the bridge (which is set up when the application launches). If the bridge is re-initialized, so is the state it collected when it was first started, so it returns null. If the bridge does not re-initialize between app launch and getInitialURL (no JS debugging session, etc), it should retain the value it collected at launch.

I recommend checking to see if your RCTBridge is getting invalidated and then re-initialized (as it does when debugging / live-loading). You can look for a line like this in your Xcode console:

Invalidating <RCTCxxBridge: 0x7fb6e7708810> (parent: <RCTBridge: 0x60000341ee60>, executor: RCTWebSocketExecutor)

If you see that, you likely need to figure out why your bridge is being torn down (and losing your launchOptions which contains the initialURL).

You can confirm that this is an issue with the bridge (on iOS) by doing the following:

Add this to the very top of your AppDelegate's application:didFinishLaunchingWithOptions:

[[[UIAlertView alloc] initWithTitle:@"AppDelegate"
                            message:[NSString stringWithFormat:@"%@", launchOptions]
                           delegate:nil
                  cancelButtonTitle:@"OK" otherButtonTitles:nil] show];

And this to RCTLinkingManager's RCT_EXPORT_METHOD(getInitialURL (RCTPromiseResolveBlock)resolve reject:(__unused RCTPromiseRejectBlock)reject)

[[[UIAlertView alloc] initWithTitle:@"getInitialURL"
                            message:[NSString stringWithFormat:@"%@", self.bridge.launchOptions]
                           delegate:nil
                  cancelButtonTitle:@"cancel" otherButtonTitles:nil] show];

Now you won't need to be connected to the debugger to see what the application sees when it launches vs. what RCTLinking fetches from those options.

Also keep in mind that if you're using a push notification service with their own SDK, they might not be using the UIApplicationLaunchOptionsURLKey to store the initial URL, so you may have to dig it out of the UIApplicationLaunchOptionsRemoteNotificationKey instead ๐Ÿ™ƒ

@donholly
Thank you! It worked.
My application uses expokit so I changed EXKernelLinkingManager.m.

from:

+ (NSURL *)initialUrlFromLaunchOptions:(NSDictionary *)launchOptions
{
  NSURL *initialUrl;

  if (launchOptions) {
    if (launchOptions[UIApplicationLaunchOptionsURLKey]) {
      initialUrl = launchOptions[UIApplicationLaunchOptionsURLKey];
    } else if (launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey]) {
      NSDictionary *userActivityDictionary = launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];

      if ([userActivityDictionary[UIApplicationLaunchOptionsUserActivityTypeKey] isEqual:NSUserActivityTypeBrowsingWeb]) {
        initialUrl = ((NSUserActivity *)userActivityDictionary[@"UIApplicationLaunchOptionsUserActivityKey"]).webpageURL;
      }
    }
  }

  return initialUrl;
}

to:

+ (NSURL *)initialUrlFromLaunchOptions:(NSDictionary *)launchOptions
{
  NSURL *initialUrl;

  if (launchOptions) {
    if (launchOptions[UIApplicationLaunchOptionsURLKey]) {
      initialUrl = launchOptions[UIApplicationLaunchOptionsURLKey];
    } else if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
      NSString *rprUrl = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey][@"rpr_url"];
      initialUrl = [NSURL URLWithString:rprUrl];
    } else if (launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey]) {
      NSDictionary *userActivityDictionary = launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];

      if ([userActivityDictionary[UIApplicationLaunchOptionsUserActivityTypeKey] isEqual:NSUserActivityTypeBrowsingWeb]) {
        initialUrl = ((NSUserActivity *)userActivityDictionary[@"UIApplicationLaunchOptionsUserActivityKey"]).webpageURL;
      }
    }
  }

  return initialUrl;
}

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions.

This is still happening in 0.61.

here is a workaround

TLDR:

on create RCTRootView, set launchOptions[UIApplicationLaunchOptionsURLKey] with the URL you want and Linking will pick up the stuff from there.

You will likely to be able to get your url from launchOptions[.localNotification] (or . remoteNotification) from one of AppDelegates if your app is launched from notification


We are using 0.59 but I assume newer version would be the same or similar

Our team encountered the same issue and we have a workaround.

We were calling getInitialURL and also addEventListener('url'...) on JS side. And we call RCTLinkingManager.application(app, open: url, options: options) with the url we get from the notification content when user response to our notification.

However, on a cold start from notification, the first one always return null and the callback in listener never get called .

On calling RCTLinkingManager.application during a cold start, we realize that the bridge is not initialized and we found not way to make sure when it is ready to send event to JS(might be some private method or notification but I just don't know). That means, at this moment, no event of type url will be received on JS side not matter how many times you call RCTLinkingManager.application. (All native module will just call enqueueJSCallbehind the scene but it appears to be noop when the bridge is not fully ready, please correct me if I am wrong)

Looking into RCTLinkingManager

getInitialURL is actually checking launchOptions[UIApplicationLaunchOptionsURLKey]

So we end up setting launchOptions[UIApplicationLaunchOptionsURLKey] before passing it to RCTRootView.

The only concern is that .localNotification marked as deprecated, we just have to use it because handling url in userNotificationCenter:didReceiveNotification will result in sending bridge event before the bridge is ready

As I still did not see any thing that works here. Adding what worked for me.

Source https://stackoverflow.com/questions/58336836/react-native-crash-after-update-to-xcode-11-0

To fix this issue, open file in

yourproject/node_modules/react-native/React/Base/RCTModuleMethod.mm.

Then correct as below:

https://github.com/facebook/react-native/pull/25146/files#diff-263fc157dfce55895cdc16495b55d190

@rokeelu @donholly Can you please help us with code snippet? How to the get the url from openUrl and then setting it in launchOptions?

You won't be setting anything yourself in launchOptions, that is set by the OS and passed to the application at launch.

From what I recall about this issue for us: it wasn't an issue with RCTLinking's getInitialURL, but with how we set up Braze.

Are you using any 3rd party providers (such as Braze, Airship, Iterable, etc) to handle your push notifications?

There were also some gotchas when running a debugger JS session as I detailed in my post:
https://github.com/facebook/react-native/issues/24429#issuecomment-550148332

I recommend adding the alerts thats I provided code for in the appropriate places to be sure your launchOptions actually has the value you're expecting.

@donholly Thanks for your reply. Actually I am trying to retrieve the URL from dynamic url(firebase). But its coming only once/twice in ten times :confused:. Still having problem and not sure how to fix this!

@manjuy124 @donholly I am having the same problem. I am getting the link 1 in 10 times or so - after AppStore installation in iOS.

@donholly Could you share what exactly was wrong with your Braze set up? I also use Braze and face this issue.

I found that I missed:
[[AppboyReactUtils sharedInstance] populateInitialUrlFromLaunchOptions:launchOptions];

@alexmbp Yep, exactly that ๐Ÿ˜„

@manjuy124 @vijayst I haven't used Firebase with RN but a quick look at their docs says:

B) At the beginning of the didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method add the following line:

[FIRApp configure];

It is recommended to add the line within the method BEFORE creating the RCTRootView. Otherwise the initialization can occur after already being required in your JavaScript code - leading to app not initialised exceptions.

are you sure you have [FIRApp configure]; BEFORE React Native is getting set up?

thanks @donholly I realised @manjuy124 is my colleague at work. He has a different github profile at work.

In our project, there was another line above [FIRApp configure].

[FIROptions defaultOptions].deepLinkURLScheme = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"PRODUCT_BUNDLE_IDENTIFIER"];

The deepLinkURLScheme was inadvertently being set to null because the key - PRODUCT_BUNDLE_IDENTIFIER was not there. By changing the key to CFBundleIdentifier, it started to work fine.

Thanks for pointing us in the right direction ;)

This is breaking for us on a brand new app.

getInitialURL returns null wether _Remote Debug_ is enabled or not.

Project info

System:
    OS: macOS Mojave 10.14.5
    CPU: (8) x64 Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz
    Memory: 25.51 MB / 16.00 GB
    Shell: 5.3 - /bin/zsh
  Binaries:
    Node: 10.16.3 - ~/.nvm/versions/node/v10.16.3/bin/node
    Yarn: 1.17.3 - ~/.nvm/versions/node/v10.16.3/bin/yarn
    npm: 6.9.0 - ~/.nvm/versions/node/v10.16.3/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  SDKs:
    iOS SDK:
      Platforms: iOS 13.2, DriverKit 19.0, macOS 10.15, tvOS 13.2, watchOS 6.1
    Android SDK:
      API Levels: 23, 25, 26, 27, 28, 29
      Build Tools: 27.0.2, 28.0.3, 29.0.0
      System Images: android-26 | Google APIs Intel x86 Atom, android-28 | Google Play Intel x86 Atom, android-29 | Intel x86 Atom_64, android-29 | Google APIs Intel x86 Atom, android-29 | Google Play Intel x86 Atom
      Android NDK: 20.0.5594570
  IDEs:
    Android Studio: 3.5 AI-191.8026.42.35.5977832
    Xcode: 11.3.1/11C504 - /usr/bin/xcodebuild
  npmPackages:
    react: 16.9.0 => 16.9.0 
    react-native: 0.61.5 => 0.61.5 
  npmGlobalPackages:
    react-native-cli: 2.0.1

When Remote Debugger is enabled the result is indeed always null, can confirm on 0.61.5

Doesn't work for me even with debugger turned off, and with live reload/fast refresh disabled.
However it works when running as a release:
react-native run-ios --device --configuration Release

As a workaround for iOS you can parse the initial URL in application:didFinishLaunchingWithOptions and pass it to your RN app in the initialProperties payload:

// Parse initialURL - copied from RCTLinkingManager getInitialURL
NSURL *initialURL = nil;
if (launchOptions[UIApplicationLaunchOptionsURLKey]) {
  initialURL = launchOptions[UIApplicationLaunchOptionsURLKey];
} else {
  NSDictionary *userActivityDictionary = launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];
  if ([userActivityDictionary[UIApplicationLaunchOptionsUserActivityTypeKey] isEqual:NSUserActivityTypeBrowsingWeb]) {
    initialURL = ((NSUserActivity *)userActivityDictionary[@"UIApplicationLaunchOptionsUserActivityKey"]).webpageURL;
  }
}

RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                 moduleName:@"MyApp"
                                          initialProperties:@{@"initialURL" : initialURL ? initialURL.absoluteString : @""}];

Then access it from your JS app:

import { View, Text, AppRegistry } from 'react-native';

const App = (props) => (
  <View>
    <Text>{props.initialURL}</Text>
  </View>
);

AppRegistry.registerComponent('Appname', () => App);

@matthieupinte Can we close this now? I've made a PR that it may return null if debugger is enabled:
https://github.com/facebook/react-native-website/pull/2151

Just discovered this gem. After trying to debug why our deep links don't work on iOS.

I think it's a tad bit of a stretch to claim you "fixed" this issue. I am on a device with debug disabled. I have Alert.alert to show the value of URLs coming from push notifications.

Nothing gets passed in at all. Are you guys sure this is "fixed"?

@scarlac Thanks for your work on that issue. I'm no longer working on the project right now, so I can't confirm it's fixed. I leave it to the community to decide :+1:

@neosavvy I meant that I fixed the documentation. The PR fixes the incorrect documentation, including warning that the debugger may cause it to return null. I have not done anything to change the implementation as such.

I believe that we still have an issue with this.

I'm trying to implement OneSignal for React Native, but, I can't make DeepLinking work properly wen iOS app is closed / killed. Initial URL is always null in this scenario.

                                     Android | iOS
              Deep link (app in bg):    โœ…   |  โœ…
             Deep link (app closed):    โœ…   |  โœ…
 Notification Deep link (app in bg):    โœ…   |  โœ…
Notification Deep link (app closed):    โœ…   |  โŒ

I don't know if it is something related to RN or OS

Could anyone help me with that?

RN 0.63.2

Just discovered this gem. After trying to debug why our deep links don't work on iOS. and here is solution

import dynamicLinks from '@react-native-firebase/dynamic-links';

dynamicLinks().getInitialLink()
      .then(link => {
    // this method will call when app is closer or killed
        console.log('dynamicLinksnew :', link);
      });
dynamicLinks().onLink(async (link) => {
      // this method call when app is in background or foreground 
     console.log('dynamicLinks :', link);
 });

@emilioheinz Do you have any ways how to resolve this issue?

@yuriiforlita Nope, no idea!

This is still an issue. I'm on react-native 0.61.5 and XCode 12.1. Even with the remote debugger disabled,Linking.getInitialURL()always returns null. I've debugged through the AppDelegate file and the RCTLinkingManager receives the correct deeplink url.

Was this page helpful?
0 / 5 - 0 ratings