Testing dynamic links and fixing react-native-firebase issues

Firebase Dynamic Links module provides a link which we can share with other apps. The recipient of the link may not have the app. In that case, the dynamic link takes the user to the App Store and prompts the user to install the app. When the app opens after the installation, it receives the link embedded within the Dynamic link. Based on the embedded link, the app takes various actions such as navigating to some screen. If it is not clear, we will go through the above steps with a concrete example.

User shares an item from the app. The app knows to navigate to the item with a link such as http://vijayt.com/product/:productId. The app wraps the above link within a dynamic link such as https://vijayt.page.link/:slug. The user shares the dynamic link to another app such as iMessage or WhatsApp.

The recipient of the iMessage message clicks on the dynamic link. If the app is already installed, the app opens up, receives the link embedded within the dynamic link and displays the product. However, if the app is not installed, the dynamic link takes the user to the App Store. After the user opens the newly installed app, the app receives the link embedded within the dynamic link and opens the relevant product.

Testing Dynamic Links when app is not installed

Testing Dynamic Links is a bit tricky. If we open the dynamic link on a browser, the browser navigates to the App Store. However, when we click on a dynamic link from another app, the app (which sent the link) opens up.

When we are doing development, development app may have a different bundle ID than the production app. Production app may have a bundle ID of com.vijayt.product whereas the development app may have a bundle ID of com.vijayt.product.dev. So, I was under the impression that we cannot test the dynamic link flow unless the app is pushed to the App Store. However, this is not the case.

When developing the app, delete the development version of the app from the device. Then click on the dynamic link. It takes us to the App Store. Instead of opening the app from App Store, run the development version of the app from XCode. The development version of the app still gets the link embedded within the dynamic link. So, we can debug the entire flow from XCode.

If testing team wants to test the flow, we can deploy the test version of the app to Test Flight. Ask the testing team to remove the app from the device. When tester clicks on the dynamic link from another app, the dynamic link takes him to the App Store. Instead of opening the app from App Store, the tester can install the app from Test Flight and open the app. The Test Flight app will receive the link embedded within dynamic link.

By the way, do not test the dynamic link by copying the link to the browser. It always goes to the App Store and not to the app even when the app is installed. Also, dynamic links don’t work on the simulator.

If you are debugging, put a breakpoint on openURL method of AppDelegate.m. That is the method that receives the link for a fresh installation of the app.

The flow chart in the Firebase documentation summarises how dynamic links work on each platform.

Getting react-native-firebase to work

I use React Native for my app. So, I picked react-native-firebase to work with dynamic links. This package has a method getInitialLink which receives the initial link when we install the app freshly or when the app opens from a closed state.

firebase
      .dynamicLinks()
      .getInitialLink()
      .then(link => {
        // do something with the link
      })

However, in my case, this method always returned null. There were two problems because of which I was not getting the initial link.

In AppDelegate.m, the method signature of openURL was incorrect. It should be:

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

After changing the method signature to the above, the app on my device started receiving the initial link. However, on some other devices, getInitialLink method of react-native-firebase was not working.

I did some debugging and found that version 6 of react-native-firebase uses an interceptor on AppDelegate. When openURL of AppDelegate.m receives the link, the interceptor (RNFBDynamicLinksAppDelegateInterceptor) also receives the link and does some processing. However on some devices, the interceptor does not trigger. So I wrote some temporary code in AppDelegate.m to do what the interceptor does.

#import <RNFBDynamicLinksAppDelegateInterceptor.h>

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
    RNFBDynamicLinksAppDelegateInterceptor *interceptor = [RNFBDynamicLinksAppDelegateInterceptor sharedInstance];
    if (url && !interceptor.initialLinkUrl)
    {
        FIRDynamicLink *dynamicLink = [[FIRDynamicLinks dynamicLinks] dynamicLinkFromCustomSchemeURL:url];
        if (dynamicLink.url) 
        {
            interceptor.initialLinkUrl = dynamicLink.url.absoluteString;
            interceptor.initialLinkMinimumAppVersion = dynamicLink.minimumAppVersion == nil ? [NSNull null] : dynamicLink.minimumAppVersion;
        }
    }
    return YES;
}

Though the above lines of code are scary, it does not do much. It makes sure that the initialLinkUrl property of the interceptor is filled up correctly.

There is also another method in AppDelegate.m named continueUserActivity. When we click on a dynamic link, this method receives control if the app is already installed. The interceptor in react-native-firebase does not get triggered when the app is in closed state. As a result, getInitialLink returned null even for this scenario. To make it work, paste the below code in AppDelegate.m.

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity
 restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
    id completion = ^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
        if (!error && dynamicLink && dynamicLink.url) {
            RNFBDynamicLinksAppDelegateInterceptor *interceptor = [RNFBDynamicLinksAppDelegateInterceptor sharedInstance];
            interceptor.initialLinkUrl = dynamicLink.url.absoluteString;
            interceptor.initialLinkMinimumAppVersion = dynamicLink.minimumAppVersion;
        }
    };
    [[FIRDynamicLinks dynamicLinks] handleUniversalLink:userActivity.webpageURL completion:completion];
   
    return YES;
}

After the above fixes in AppDelegate, getInitialLink of react-native-firebase started to work well. Also the work-around that I suggested in my previous article is not needed.

Testing all the flows with Dynamic links

There are three code flows to test with Dynamic links.

  • When app is not installed,
  • App is installed but in closed state
  • And app is open

To test the first scenario, delete the app. Click on the dynamic link. Then open the app from Xcode in debug mode or from Test Flight. Verify if the app receives the embedded link. The code flow for this is openURL method in AppDelegate and then getInitialLink in react-native-firebase.

To test the second scenario, close the app with App Switcher. Click on the dynamic link and verify if the app receives the embedded link. The code flow for this is continueUserActivity in AppDelegate and getInitialLink in react-native-firebase.

To test the third scenario, open the app. Then click on the dynamic link. Verify if the app receives the embedded link. The code flow for this is continueUserActivity in AppDelegate and onLink handler in react-native-firebase.

Test all the three scenarios as they have different code flows in your app.

Related Posts

5 thoughts on “Testing dynamic links and fixing react-native-firebase issues

  1. Thanks for this! By any chance were you able to test it with react-native-firebase^7.0.1? I’m still struggling with the same issue there unfortunately 🙁

  2. Thanks for this! I wasn’t able to get this solution workin react-native-firebase^7.0.1 – have you had any luck with that version? Thanks again

  3. I have added your code into my AppDelegate.m and it cause my app to get an initialLink if the app is already installed. But I still get `null` if the app is not installed (use the link -> open app store -> install by xCode -> null).

    Could you help me with that?

Leave a Reply

Your email address will not be published.