react-native: React native deep linking not working when app is in background state

šŸ› Bug Report

When the app is closed, I am able to get deep link url that is clicked by Linking.getInitialURL(). When the app is in the background state, then nothing is mounted. So, I am not able to get the url even by the Linking.addEventListener(ā€˜urlā€™, method_name).

What is the way to achieve this?

To Reproduce

Expected Behavior

Code Example

componentDidMount() { Linking.addEventListener(ā€˜urlā€™, this._handleOpenURL); }, componentWillUnmount() { Linking.removeEventListener(ā€˜urlā€™, this._handleOpenURL); }, _handleOpenURL(event) { console.log(event.url); } I have added this code in app.js

Environment

React Native Environment Info: System: OS: Linux 4.15 Ubuntu 18.04.1 LTS (Bionic Beaver) CPU: (4) x64 IntelĀ® Coreā„¢ i3-6098P CPU @ 3.60GHz Memory: 1.73 GB / 15.57 GB Shell: 4.4.19 - /bin/bash Binaries: Node: 10.11.0 - /usr/bin/node npm: 6.7.0 - /usr/bin/npm npmPackages: react: 16.6.0-alpha.8af6728 => 16.6.0-alpha.8af6728 react-native: ^0.58.5 => 0.58.5 npmGlobalPackages: create-react-native-app: 2.0.2 react-native-cli: 2.0.1 react-native-rename: 2.4.0 react-native-slideshow: 1.0.1

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 21
  • Comments: 64 (1 by maintainers)

Most upvoted comments

For me, the issue was that although the app would come to the foreground on clicking on the app link (say from an SMS), the callback passed to Linking.addEventListener never got fired.

So what I ended up doing is this: add an AppState listener to check the url from Linking.getInitialURL, and if it is a valid URL, redirect accordingly.

componentDidMount() {
  // triggered when react native boots
  this._checkInitialUrl()

  AppState.addEventListener('change', this._handleAppStateChange)
  // never gets fired on Android, hence useless to me
  // Linking.addEventListener('url', url => console.log('url received: ', url))
}

componentWillUnmount() {
  AppState.removeEventListener('change', this._handleAppStateChange)
}

_handleAppStateChange = async (nextAppState) => {
  if (
    this.state.appState.match(/inactive|background/) &&
    nextAppState === 'active'
  ) {
    this._checkInitialUrl()
  }
  this.setState({ appState: nextAppState })
}

_checkInitialUrl = async () => {
  const url = await this._getInitialUrl()
  this._handleUrl(url)
}

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

_handleUrl = (url) => {
  // write your url handling logic here, such as navigation
  console.log('received URL: ', url)
}

Pls try this. componentDidMount() { Linking.addEventListener(ā€œurlā€, this.handleOpenURL); this.handleDeepLinkingRequests(); } handleDeepLinkingRequests = () => { Linking.getInitialURL() .then(url => { if (url) { this.handleOpenURL(url); } }) .catch(error => { // Error handling }); } };

handleOpenURL = (url) => { // your navigation logic goes here }

Notes: -> Linking.getInitialURL() method should only be called for the first time when the app is launched via app-swap -> For subsequent app-swap calls, handleOpenURL() method will be called as it is configured with linking event listener. -> remember to unsubscribe linking events in componentwillunmount()

@devanshu23598

try to add this code below in you MainActivity.java

  import android.content.Intent;
  import android.net.Uri;
  import com.facebook.react.bridge.Arguments;
  import com.facebook.react.bridge.WritableMap;
  import com.facebook.react.modules.core.DeviceEventManagerModule;
  ...
  @Override
  public void onNewIntent(Intent intent) {
      if (intent.getData() != null) {
        Uri deepLinkURL = intent.getData();
        // note deeplink_identifier means the identity that you register in the manifest.
        if (deepLinkURL.toString().contains("deeplink_identifier")) {
            // Create map for params
            WritableMap event = Arguments.createMap();
            // Put data to map
            event.putString("url", deepLinkURL.toString());
            // Get EventEmitter from context and send event thanks to it
            getReactInstanceManager().getCurrentReactContext()
                    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit("url", event);
        } else {
           // to handle other deeplink that not related to the defined deeplink identifier such as notification
           setIntent(intent);
        }
      } 
  }

Is there an update on this issue ? Deep linking is an important feature, and this issue makes it pretty much unusable

@apamphilon try the below code. componentDidMount(){ this._checkInitialUrl() AppState.addEventListener(ā€˜changeā€™, this._handleAppStateChange) Linking.getInitialURL().then(async (url) => { if (url) { console.log('Initial url is: ā€™ + url); } }).catch(err => console.error(ā€˜An error occurredā€™, err)); } componentWillUnmount() { AppState.removeEventListener(ā€˜changeā€™, this._handleAppStateChange) }

   _handleAppStateChange = async (nextAppState) => {
     if (
       this.state.appState.match(/inactive|background/) &&
       nextAppState === 'active'
     ) {
       this._checkInitialUrl()
     }
     this.setState({ appState: nextAppState })
   }

   _checkInitialUrl = async () => {
     const url = await this._getInitialUrl()
     this._handleUrl(url)
   }

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

this is working if the app is in background state too.

after investigation, I found the solution, add this code in your AppDelegate.m

#import "RCTLinkingManager.h"
...
- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
            options:(NSDictionary<NSString *, id> *)options {
  return [RCTLinkingManager application:application openURL:url options:options];
}

reference: https://v5.rnfirebase.io/docs/v5.x.x/links/ios

So, apparently, it works just fine if you turn off the debugger

Same issue, seems like Linking.getInitalURL() works if the app is not yet launched, and Linking.addEventListener('url' callback) works if the app is in foreground but neither seem to work for apps in background state.

Found that while running with the debugger in iOS simulator it will work through the event listener but not with Linking.getInitialURL().

Also, my team found that without the debugger it works fine.

Doing a trace, it correctly sets the bridge values/info but when calling the method getInitialURL, the bridge no longer holds those values.

It seems that there is a reset between both moments when the Debugger is present.

I hope this can be a hint to investigate the issue and for the solution. I will post more info if I find something more.

Not sure if others are having this problem, but for me the event listener doesnā€™t work (Linking.addEventListener('url', link => ({ link }));) Also, as has been mentioned, Linking.getInitialUrl only works on cold start, not from background. Edit: I donā€™t have the debugger running

For those that are still facing the issue of not getting an event when a url is opened when the app is in background, I have a workaround for iOS.

in openURL in AppDelegate, add this line: [[NSUserDefaults standardUserDefaults] setObject:url.absoluteString forKey:@"deepLinkURL"];

In your component in JS, add this:

Settings.watchKeys(["deepLinkURL"], () => {
      const url = Settings.get("deepLinkURL");
});

Make sure you add ā€œRCTSettingsā€ as a sub-pod in your podfile.

I give you my hook that works grate on Android and IOS.

import {useEffect, useCallback} from 'react';
import {Linking} from 'react-native';
import useAppState from './useAppState';

const useDeepLink = (callback: (url: string) => void) => {
  const appState = useAppState();

  const handleDeepLink = useCallback(
    link => {
      if (link) {
        const url = typeof link === 'string' ? link : link.url;
        console.log({deeplink: url});

        callback(url);
      }
    },
    [callback],
  );

  useEffect(() => {
    if (appState === 'background') {
      Linking.getInitialURL().then(handleDeepLink);
    }
  }, [appState, handleDeepLink]);

  useEffect(() => {
    Linking.getInitialURL().then(handleDeepLink);

    Linking.addListener('url', handleDeepLink);

    return () => {
      Linking.removeEventListener('url', handleDeepLink);
    };
  }, [handleDeepLink]);
};

export default useDeepLink;

Also facing this issue. The event listener fails to fire if the app is not already open.

for me, the issue with using appState as a workaround is that on iOS Linking.addEventListener(ā€˜urlā€™) will get the latest url sent to the app - this is helpful if the app is used to share URLs. So each time a URL is sent to the app, it receives the correct one. Using appState and getInitialURL you only ever get the first url the app was launched with.

Is there any update on getting Linking.addEventListener(ā€˜urlā€™) working properly on Android?

I suggest you to follow the rn-firebase guide to do it right. I do that and my hook ends as follow:

const useDeepLink = (callback: (url: string) => void) => {
  const handleDynamicLink = useCallback(
    (link: any) => {
      console.log({link});
      if (link && link.url) {
        callback(link.url);
      }
    },
    [callback],
  );

  useEffect(() => {
    dynamicLinks()
      .getInitialLink()
      .then(handleDynamicLink);

    const unsubscribe = dynamicLinks().onLink(handleDynamicLink);

    return () => unsubscribe();
  }, [handleDynamicLink]);
};

This worked for me for both background and foreground ` import React, { useEffect, useCallback, useRef, Fragment } from ā€˜reactā€™; import { Linking, AppState } from ā€˜react-nativeā€™;

const useDeepLink = (props) => { const appState = useRef(AppState.currentState), { callback } = props, handleDeepLink = useCallback( link => { if (link) { const url = typeof link === ā€˜stringā€™ ? link : link.url; callback(url); } }, [], );

useEffect(() => {
    if (appState === 'background') {
        Linking.getInitialURL().then(handleDeepLink);
    }
}, [appState, handleDeepLink]);

useEffect(() => {
    Linking.getInitialURL().then(handleDeepLink);

    Linking.addListener('url', handleDeepLink);

    return () => {
        Linking.removeEventListener('url', handleDeepLink);
    };
}, [handleDeepLink]);
return (
    <Fragment />
)

};

export default useDeepLink; `

Facing same issue 0.62.0

Facing the same issue. Not working for iOS. Cannot get the url (always null) when the app is closed when the link happens.

React Native Environment Info: System: OS: macOS 10.14.6 CPU: (8) x64 IntelĀ® Coreā„¢ i7-6700HQ CPU @ 2.60GHz Memory: 353.30 MB / 16.00 GB Shell: 5.3 - /bin/zsh Binaries: Node: 11.9.0 - /usr/local/bin/node npm: 6.5.0 - /usr/local/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman SDKs: iOS SDK: Platforms: iOS 13.1, DriverKit 19.0, macOS 10.15, tvOS 13.0, watchOS 6.0 IDEs: Xcode: 11.1/11A1027 - /usr/bin/xcodebuild npmPackages: react: 16.8.3 => 16.8.3 react-native: ^0.59.9 => 0.59.9 npmGlobalPackages: react-native-cli: 2.0.1

=======>>>Method to catch Deep link when App opens <<<======= useEffect(() => { // Get the deep link used to open the app const getUrl = async () => { const universalLink = await Linking.getInitialURL();

  if (universalLink === null) {
    return;
  }
  //handle universal link
  if (universalLink.includes('loginsuccess')) {
    console.log('INSIDE-------loginsuccess--useEffect---One');
    Alert.alert(initialUrl);
  }
};

getUrl();

},[]);

=======>>>Method to catch Deep link when App is In Background <<<=======

useEffect(() => {

const urlListener = Linking.addEventListener('url', (url: object) => {
  // console.log('this is the url---2: ', url.url);

  let urlString = JSON.stringify(url.url);

  if (urlString === null) {
    return;
  }

  if (urlString.includes('loginsuccess')) {
    Alert.alert(urlString);
    
  }
  // Alert.alert(JSON.stringify(url));
  console.log('INSIDE-------loginsuccess----useEffect---Two');
});

return () => {
  urlListener.remove();
};

}, []);

Facing the same issue on Android. The disabling debugger doesnā€™t help. The suggestion to access the URL with AppState & Linking.getInitialURL() doesnā€™t help either.

// Add Listener to catch a state


componentDidMount() {
   Linking.addEventListener('url', this._handleOpenURL);
},
componentWillUnmount() {
   Linking.removeEventListener('url', this._handleOpenURL);
},
_handleOpenURL(event) {
   console.log(event.url);
}

@RZulfikri Thank you for your response!! I added the code in MainActivity.java and it works fine when running through terminal. I want to open the app through web browser. That not work.

@DalbirKaur Thanks for the suggestion. I did try and do it this way but found that if the deep link has already been navigated to and then you switch screens and put the app back into the background and then relaunch, the event listener will fire again and navigate to that screen again.

Any thoughts?