react-native icon indicating copy to clipboard operation
react-native copied to clipboard

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

Open DalbirKaur opened this issue 6 years ago • 59 comments

🐛 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(R) Core(TM) 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

DalbirKaur avatar Apr 26 '19 12:04 DalbirKaur

Can you run react-native info and edit your issue to include these results under the Environment section?

If you believe this information is irrelevant to the reported issue, you may write `[skip envinfo]` alongside an explanation in your Environment: section.

react-native-bot avatar Apr 26 '19 13:04 react-native-bot

It looks like you are using an older version of React Native. Please update to the latest release, v0.59 and verify if the issue still exists.

The "Resolution: Old Version" label will be removed automatically once you edit your original post with the results of running `react-native info` on a project using the latest release.

react-native-bot avatar Apr 26 '19 13:04 react-native-bot

are you added

<activity
  android:name=".MainActivity"
  android:launchMode="singleTask">

in AndroidManifest.xml ? (https://facebook.github.io/react-native/docs/linking)

maleking avatar Apr 28 '19 20:04 maleking

Yes, I have already added this in 'my_project/android/app/src/main/AndroidManifest.xml. Example of code:

<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize"
android:launchMode="singleTask">

DalbirKaur avatar Apr 29 '19 04:04 DalbirKaur

Any updates on this?

glocore avatar May 08 '19 04:05 glocore

@DalbirKaur were you able to solve for this?

glocore avatar May 08 '19 04:05 glocore

@platonish, I am not able to get any lead yet.

DalbirKaur avatar May 08 '19 06:05 DalbirKaur

I'm having the same issue in android I'm not able to get the URL to make any action "react": "^16.8.3", "react-native": "^0.59.2",

janet-rivas avatar May 08 '19 20:05 janet-rivas

@DalbirKaur can you attach AndroidManifest.xml file ?

maleking avatar May 09 '19 09:05 maleking

<permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature" />

<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

<application
  android:name=".MainApplication"
  android:label="@string/app_name"
  android:icon="@mipmap/ic_launcher_round"
  android:allowBackup="false"
  android:theme="@style/AppTheme">
  <activity
    android:name=".MainActivity"
    android:label="@string/app_name"
    android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
      android:screenOrientation="portrait"
    android:windowSoftInputMode="adjustResize"
      android:launchMode="singleTask">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
            <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
        </intent-filter>
      <intent-filter>
          <data android:scheme="http" android:host="MY_HOST_NAME" />
          <action android:name="android.intent.action.VIEW" />
          <category android:name="android.intent.category.BROWSABLE"/>
          <category android:name="android.intent.category.DEFAULT"/>
      </intent-filter>
      <intent-filter android:label="@filter_view_http_gizmos">
          <action android:name="android.intent.action.VIEW" />
          <category android:name="android.intent.category.DEFAULT" />
          <category android:name="android.intent.category.BROWSABLE" />
          <!-- Accepts URIs that begin with "http://www.example.com/gizmos” -->
          <data android:scheme="http"
              android:host="www.tdemo.com"
              android:pathPrefix="/td" />
          <!-- note that the leading "/" is required for pathPrefix-->
      </intent-filter>
      <intent-filter android:label="Turn Doctor">
          <action android:name="android.intent.action.VIEW" />
          <category android:name="android.intent.category.DEFAULT" />
          <category android:name="android.intent.category.BROWSABLE" />
          <!-- Accepts URIs that begin with "example://gizmos” -->
          <data android:scheme="example"
              android:host="gizmos" />
      </intent-filter>
  </activity>
  <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
    <meta-data  android:name="com.dieam.reactnativepushnotification.notification_channel_name"
        android:value="YOUR NOTIFICATION CHANNEL NAME"/>
    <meta-data  android:name="com.dieam.reactnativepushnotification.notification_channel_description"
        android:value="YOUR NOTIFICATION CHANNEL DESCRIPTION"/>
    <!-- Change the resource name to your App's accent color - or any other color you want -->
    <meta-data  android:name="com.dieam.reactnativepushnotification.notification_color"
        android:resource="@android:color/white"/>

    <!-- < Only if you're using GCM or localNotificationSchedule() > -->
    <receiver
        android:name="com.google.android.gms.gcm.GcmReceiver"
        android:exported="true"
        android:permission="com.google.android.c2dm.permission.SEND" >
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <category android:name="${applicationId}" />
        </intent-filter>
    </receiver>
    <!-- < Only if you're using GCM or localNotificationSchedule() > -->

    <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
    <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>
    <service android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationRegistrationService"/>

    <!-- < Only if you're using GCM or localNotificationSchedule() > -->
    <service
        android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerServiceGcm"
        android:exported="false" >
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
        </intent-filter>
    </service>
    <!-- </ Only if you're using GCM or localNotificationSchedule() > -->

    <!-- < Else > -->
    <service
        android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
        android:exported="false" >
        <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT" />
        </intent-filter>
    </service>


    <service android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationActionService" />
    <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationActionHandlerReceiver"
        android:exported="true">
        <intent-filter>
            <action android:name="${applicationId}.firstAction" />
            <action android:name="${applicationId}.secondAction" />
        </intent-filter>
    </receiver>
</application>

@maleking, I have pasted the content of AndroidManifest.xml file of my project. Because I was unable to attach the .xml file.

DalbirKaur avatar May 10 '19 04:05 DalbirKaur

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)
}

glocore avatar May 14 '19 09:05 glocore

Same issue here on 59.8.

jwaldrip avatar May 31 '19 16:05 jwaldrip

Does any one have a work around on this? I'm facing the same issue for Android devices. And @glocore 's solution is not work for me. It will fire the _checkInitialUrl even if I don't click on any link

tranthienhao avatar Jun 27 '19 07:06 tranthienhao

@tranthienhao I am also wondering this. Does anyone have any light to shed on this? Is this a bug, or does Linking.addEventListener('url', method_name). not work on Android?

apamphilon avatar Jul 30 '19 18:07 apamphilon

@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.

DalbirKaur avatar Jul 31 '19 07:07 DalbirKaur

@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?

apamphilon avatar Aug 01 '19 08:08 apamphilon

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?

SirCameron avatar Aug 22 '19 11:08 SirCameron

I am also facing same issue Linking.addEventListener('url') returns the initial url only, or Linking.getInitialURL() returns null. Linking.removeEventListener('url') doesnt work, how can we remove the existing url and get the latest url.

ppv94 avatar Sep 03 '19 08:09 ppv94

Does anyone can solve keep getting the old url instead of latest url issue?

Kingsace avatar Sep 12 '19 03:09 Kingsace

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

cvarley100 avatar Sep 16 '19 14:09 cvarley100

facing the same issue. it is working fine for iOS for me by the way.

aamir-munir avatar Oct 23 '19 22:10 aamir-munir

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(R) Core(TM) 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

joaocac avatar Oct 28 '19 10:10 joaocac

When the app is in background getinitialurl method returns null it is not working in android any one find the solution for it..even though i used the app state it is not working for me

srichallamalla935 avatar Oct 31 '19 10:10 srichallamalla935

These Listeners calls when app is in background.Below code worked for me when app is in background in android

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

srichallamalla935 avatar Nov 01 '19 06:11 srichallamalla935

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

JulianKingman avatar Nov 21 '19 00:11 JulianKingman

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.

joaocac avatar Nov 21 '19 22:11 joaocac

@joaocac Found mostly the same on my first run through this, but now after using a combination of Linking.addEventListenerand Linking.getInitialURL() I only don't see a URL during cold-start. All other initiations work fine and I see the deep-linked URL. On RN 0.59, iOS.

perry-mitchell avatar Nov 29 '19 10:11 perry-mitchell

If using firebase dynamic links then you can use onLink() event listener for latest url. Also make sure you call getInitialLink() when component mounts.

aathapa avatar Dec 04 '19 04:12 aathapa

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.

ryanoconnor7 avatar Dec 13 '19 17:12 ryanoconnor7

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()

krishnakumarrk avatar Jan 15 '20 00:01 krishnakumarrk