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

Linking url event never fires

Open leoparis89 opened this issue 3 years ago • 9 comments

Description

Url event never fires

I followed the indicated setup to handle deep links in react native.

But when I have the app open in background or foreground and I execute command npx uri-scheme open "mychat://bar" --ios, url event doesn't fire.

I saw there is a similar issue in react-navigation, but the fix indicated for AppDelegate.m doesn't work for me.

Version

0.70

Output of npx react-native info

System: OS: macOS 12.5.1 CPU: (16) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz Memory: 59.18 MB / 16.00 GB Shell: 5.8.1 - /bin/zsh Binaries: Node: 16.13.1 - /usr/local/opt/node@16/bin/node Yarn: 1.22.11 - /usr/local/bin/yarn npm: 8.1.2 - /usr/local/opt/node@16/bin/npm Watchman: Not Found Managers: CocoaPods: 1.11.2 - /usr/local/bin/pod SDKs: iOS SDK: Platforms: DriverKit 21.4, iOS 15.5, macOS 12.3, tvOS 15.4, watchOS 8.5 Android SDK: API Levels: 28, 31, 33 Build Tools: 30.0.2, 31.0.0, 33.0.0 System Images: android-31 | Google APIs Intel x86 Atom_64, android-31 | Google Play Intel x86 Atom_64 Android NDK: Not Found IDEs: Android Studio: 2021.2 AI-212.5712.43.2112.8815526 Xcode: 13.4.1/13F100 - /usr/bin/xcodebuild Languages: Java: 13.0.2 - /usr/bin/javac npmPackages: @react-native-community/cli: Not Found react: 18.1.0 => 18.1.0 react-native: 0.70.0 => 0.70.0 react-native-macos: Not Found npmGlobalPackages: react-native: Not Found

Steps to reproduce

clone repo https://github.com/leoparis89/deeplink

  1. yarn
  2. cd ios && pod install
  3. yarn ios
  4. run command npx uri-scheme open "mychat://bar" --ios. App gets focused but url event never fires in ./useDeepLink.js line 15.

Snack, code example, screenshot, or link to a repository

https://github.com/leoparis89/deeplink

leoparis89 avatar Sep 15 '22 15:09 leoparis89

I want to add to this issue what I researched, having similar issue:

  • When the app is not running at all, and you select "share/open with" your app the following happens:
    • First, the (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions is called, having the path to the file in the launch options under "UIApplicationLaunchOptionsURLKey" key. This path is not accessible (permission-wise to the app). This path is also what you'd get if you call Linking.getInitialURL
    • Then, the (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:... is called. This method receives a different URL, which is a copy of the file in your app's temp/inbox folder - fully accessible to your app. And as suggested, it is implemented with one line: return [RCTLinkingManager application:application openURL:url options:options];, which triggers RCTLinkingManager to send notification via NSNotificationCenter with that (good) url. Now comes the issue: at this point in time, the JS was not yet started and the JS part of the app did not register to listen to "url" event. That is why RCTEventEmitter did not call RCTLinkingManager.startObserving and the notification with the url is lost. and even if it would have been received, the JS did not register yet, and it would not have been fired to the JS part.

Here is how I solved it (and I suggest you implement something more generic/robust for Linking:

  • when RCTLinkingManager application:application openURL:url options:options is called, I save the url in a object's local variable. the when RCTLinkingManager.startObserving is called, if this variable is not nil, I fire the url event.

  • line 19: static NSString *savedEvent = nil; at startObserving, at its end:

if (savedEvent != nil) {
        NSDictionary<NSString *, id> *payload = @{@"url": savedEvent};
        [self sendEventWithName:@"url" body:payload];
        savedEvent = nil;
    }

at application:openURL:options

    // if event was missed, save it for later firing
    savedEvent = URL.absoluteString;

I hope this helps.

ariel-bentu avatar Sep 19 '22 08:09 ariel-bentu

I want to add to this issue what I researched, having similar issue:

  • When the app is not running at all, and you select "share/open with" your app the following happens:

    • First, the (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions is called, having the path to the file in the launch options under "UIApplicationLaunchOptionsURLKey" key. This path is not accessible (permission-wise to the app). This path is also what you'd get if you call Linking.getInitialURL
    • Then, the (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:... is called. This method receives a different URL, which is a copy of the file in your app's temp/inbox folder - fully accessible to your app. And as suggested, it is implemented with one line: return [RCTLinkingManager application:application openURL:url options:options];, which triggers RCTLinkingManager to send notification via NSNotificationCenter with that (good) url. Now comes the issue: at this point in time, the JS was not yet started and the JS part of the app did not register to listen to "url" event. That is why RCTEventEmitter did not call RCTLinkingManager.startObserving and the notification with the url is lost. and even if it would have been received, the JS did not register yet, and it would not have been fired to the JS part.

Here is how I solved it (and I suggest you implement something more generic/robust for Linking:

  • when RCTLinkingManager application:application openURL:url options:options is called, I save the url in a object's local variable. the when RCTLinkingManager.startObserving is called, if this variable is not nil, I fire the url event.
  • line 19: static NSString *savedEvent = nil; at startObserving, at its end:
if (savedEvent != nil) {
        NSDictionary<NSString *, id> *payload = @{@"url": savedEvent};
        [self sendEventWithName:@"url" body:payload];
        savedEvent = nil;
    }

at application:openURL:options

    // if event was missed, save it for later firing
    savedEvent = URL.absoluteString;

I hope this helps.

Could you specify in which file those modifications need to be made ? Ideally could you also post a snippet of the modified code ?

Haven't been able to reproduce your instructions. Thanks

leoparis89 avatar Sep 21 '22 09:09 leoparis89

The file is react-native/Libraries/LinkingIOS/RCTLinkingManager.mm

#import <React/RCTLinkingManager.h>

#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTBridge.h>
#import <React/RCTUtils.h>
#import <React/RCTLog.h>

#import "RCTLinkingPlugins.h"

static NSString *const kOpenURLNotification = @"RCTOpenURLNotification";

static NSString *savedEvent = nil;
- (void)startObserving
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(handleOpenURLNotification:)
                                                 name:kOpenURLNotification
                                               object:nil];
    if (savedEvent != nil) {
        NSDictionary<NSString *, id> *payload = @{@"url": savedEvent};
        [self sendEventWithName:@"url" body:payload];
        savedEvent = nil;
    }
}
+ (BOOL)application:(UIApplication *)app
            openURL:(NSURL *)URL
            options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
    postNotificationWithURL(URL, self);
    
    // if event was missed, save for firing later if event "url" is registered
    savedEvent = URL.absoluteString;
    
    return YES;
}

ariel-bentu avatar Sep 22 '22 04:09 ariel-bentu

@ariel-bentu I try you solution , but url event doesn't fire when I have the app open in background or foreground. (when app closed , it's ok). Do you have any suggestion ?

edwardhsueh avatar Sep 22 '22 18:09 edwardhsueh

do you have this method added in your AppDelegate.mm:

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

This is a prerequisite, also documented by react native (see here)

ariel-bentu avatar Sep 22 '22 19:09 ariel-bentu

Yes, I do follow the document but still fail. I can't run in simulator, so I use browser with button to call the url scheme. if app is closed, it works fine. But if app is in background, the url scheme called just bring app to foreground, but the url listener is not fired. My RN is 0.68.2, older than yours. Will this be a problem? By the way, can your app receive url event when it is in background?

edwardhsueh avatar Sep 23 '22 00:09 edwardhsueh

Hi @edwardhsueh, I was using react native 0.68.0 and facing the same problem One thing that you can try is adding the below code to the location that specified by React Navigation Documentation as I am not familiar with Swift and placing the code in the wrong location

// Add the header at the top of the file:
#import <React/RCTLinkingManager.h>

// Add this inside `@implementation AppDelegate` above `@end`:
- (BOOL)application:(UIApplication *)application
   openURL:(NSURL *)url
   options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
  return [RCTLinkingManager application:application openURL:url options:options];
}

It's working for me now

vickfan avatar Oct 06 '22 01:10 vickfan

The file is react-native/Libraries/LinkingIOS/RCTLinkingManager.mm

#import <React/RCTLinkingManager.h>

#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTBridge.h>
#import <React/RCTUtils.h>
#import <React/RCTLog.h>

#import "RCTLinkingPlugins.h"

static NSString *const kOpenURLNotification = @"RCTOpenURLNotification";

static NSString *savedEvent = nil;
- (void)startObserving
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(handleOpenURLNotification:)
                                                 name:kOpenURLNotification
                                               object:nil];
    if (savedEvent != nil) {
        NSDictionary<NSString *, id> *payload = @{@"url": savedEvent};
        [self sendEventWithName:@"url" body:payload];
        savedEvent = nil;
    }
}
+ (BOOL)application:(UIApplication *)app
            openURL:(NSURL *)URL
            options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
    postNotificationWithURL(URL, self);
    
    // if event was missed, save for firing later if event "url" is registered
    savedEvent = URL.absoluteString;
    
    return YES;
}

This solution is not working for me either.

I added the lines you described in RTCLinkingManager.mm. I added the lines in AppDelegate.mm as described in RN Linking docs.

Is there possibly some extra setup to do?

I find it surprising that a major feature like deeplinking is currently broken. Any clue on a possible workaround?

leoparis89 avatar Oct 06 '22 12:10 leoparis89

Hi, Finally, I found that linking works correctly on iOS/android when I upgrade react-native from 0.68 to 0.70.1 and follow the setting requirement for Linking

edwardhsueh avatar Oct 18 '22 10:10 edwardhsueh

Hi, Finally, I found that linking works correctly on iOS/android when I upgrade react-native from 0.68 to 0.70.1 and follow the setting requirement for Linking

Does does it work when app is open in background ? Or only when it was previously closed ?

On my side, even with RN 0.70.6 url event never fires when app is already in background.

leoparis89 avatar Dec 19 '22 15:12 leoparis89

Hi, Finally, I found that linking works correctly on iOS/android when I upgrade react-native from 0.68 to 0.70.1 and follow the setting requirement for Linking

Could you post a link to a repo with the working config? I tried the specific version 0.70.1 and url event still doesn't fire if app is open in background.

leoparis89 avatar Dec 19 '22 17:12 leoparis89

@leoparis89 :
the following is my react related repo.
"react": "18.1.0", "react-native": "0.70.1", I have url event fired in background by using using Linking.addEventListener('url', callback)

Does your problem occurs both in iOS and Android platform ?

edwardhsueh avatar Dec 23 '22 06:12 edwardhsueh

Do we have some update? I have same problem.It works on android in killed and background state but on iOS doesn't work in background/foreground. My enviroment is : "react": "18.0.0", "react-native": "0.70.6" I use also some firebase packeges like (react-native-firebase/auth,react-native-firebase/app...) could this be a problem?

SerafinAna avatar Dec 29 '22 08:12 SerafinAna

Solved. It was an error on my part:

the bellow snippet must be added in @implementatione AppDelegat not @interface AppDelegate

// For deep links
- (BOOL)application:(UIApplication *)application
   openURL:(NSURL *)url
   options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
  return [RCTLinkingManager application:application openURL:url options:options];
}
// For universal links
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
 restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
 return [RCTLinkingManager application:application
                  continueUserActivity:userActivity
                    restorationHandler:restorationHandler];
}

leoparis89 avatar Jan 10 '23 12:01 leoparis89

Solved. It was error in my project. After upgrade of React Native version, I forgot to delete old settings file for iOS. (AppDelegat.m) So, in project were AppDelegat.m and AppDelegat.mm. New version of React Native needs file with new extension .mm instead of .m and this caused problems with deep linking. When I removed old file with extension .m and move all logic for deep linking in file with extension .mm I got BINGO !!!

SerafinAna avatar Jan 11 '23 15:01 SerafinAna

@SerafinAna I upgrade to RN 0.71.6 and move the deep linking code from AppDelegate.m to AppDelegate.mm but the link is still unable to open i got this

Error opening URL: [Error: Unable to open URL: com.xxxxx://dev-xx.com/ios/com.xxxxx/login-callback?code=xxxxx. Add com.xxxxx to LSApplicationQueriesSchemes in your Info.plist.]

here is my AppDelegate.mm in case I missed out anything

#import "AppDelegate.h"

#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTLinkingManager.h>


@implementation AppDelegate

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

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


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  self.moduleName = @"nativeIntegration";
  // You can add your custom initial props in the dictionary below.
  // They will be passed down to the ViewController used by React Native.
  self.initialProps = @{};

  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off.
///
/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html
/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture).
/// @return: `true` if the `concurrentRoot` feature is enabled. Otherwise, it returns `false`.
- (BOOL)concurrentRootEnabled
{
  return true;
}

@end

LayMui avatar Apr 16 '23 07:04 LayMui

@ariel-bentu how can you modified the library node_modules/react-native/Libraries/LinkingIOS/RCTLinkingManager.mm

this file will be restored when I run yarn

anyway, it will cause this build error in xcode Use of undeclared identifier 'savedEvent'

so this is deep linking broken issue is still an issue in react native I am using

"react": "18.2.0", "react-native": "^0.71.6",

LayMui avatar Apr 16 '23 08:04 LayMui

We're affected as well by the bug.

pyrsmk avatar Apr 25 '23 16:04 pyrsmk

can we reopen this issue? this issue is not resolved yet

LayMui avatar Apr 27 '23 02:04 LayMui

I also faced the same problem. In Android works but in iOS Linking.addEventListener is not working.

My RN Version: "0.70.6"

aiwahchong avatar May 03 '23 06:05 aiwahchong

I manage to get it work on iOS need to add the url to the xcode setting refer to https://reactnavigation.org/docs/deep-linking/. At xcode, go to Targets, under the Info tab, go to URL Types, add the appID to the identifier and URL Scheme field the appID would be com.RNProject

however my android does not work

I got a build error on android android/app/src/main/java/com/xxx/usermanagement/testapp/MainApplication.java:61: error: cannot find symbol ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); ^ symbol: variable ReactNativeFlipper location: class MainApplication 1 error

LayMui avatar May 09 '23 06:05 LayMui

if you use react native +0.71

change AppDelegate.m to AppDelegate.mm

// Add the header at the top of the file:
#import <React/RCTLinkingManager.h>

// Add this inside `@implementation AppDelegate` above `@end`:
- (BOOL)application:(UIApplication *)application
   openURL:(NSURL *)url
   options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
  return [RCTLinkingManager application:application openURL:url options:options];
}
// Add this inside `@implementation AppDelegate` above `@end`:
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
 restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
 return [RCTLinkingManager application:application
                  continueUserActivity:userActivity
                    restorationHandler:restorationHandler];
}

moustaouiSalaheddine avatar Aug 02 '23 08:08 moustaouiSalaheddine

I have the same issue on android - Linking.addEventListener('url', callback) doesn't fire callback when the app in background. "expo": "~48.0.18", "react-native": "0.71.8", "react": "18.2.0"

Does anyone have decision?

DenisBakh avatar Sep 14 '23 11:09 DenisBakh