Universal Links (App Links) in React Native for iOS & Android

Jake Curreri
SmallWorld
Published in
7 min readDec 6, 2021

Introduction

Universal links (and deep links) are an excellent tool to increase engagement across mobile app features. “Share with Friends” and cross-promotional marketing are all possible because of Universal Links.

As interoperability evolves, the feature set of deep linking for both iOS and Android equally evolves. Simply put, users expect to be able to share content they love in the easiest way possible.

This guide is written to help you navigate common pitfalls in universal & deep linking. While the article is intended for React Native, it is also useful for native iOS and Android development.

iOS Basic Setup

This setup is for both Universal Links and Deep Links.

Configure the native iOS app to open based on the yourapp:// URI scheme.

You’ll need to link RCTLinking to your project by following the steps described here. To be able to listen to incoming app links, you’ll need to add the following lines to YourApp/ios/YourApp/AppDelegate.m.

If you’re targeting iOS 9.x or newer:

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

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

If you’re targeting iOS 8.x or older, you can use the following code instead:

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

// Add this above `@end`:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [RCTLinkingManager application:application openURL:url
sourceApplication:sourceApplication annotation:annotation];
}

To enable Universal Links, you’ll need to add the following code as well:

// Add this 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];
}

Now you need to add the scheme to your project configuration.

The easiest way to do this is with the uri-scheme package: npx uri-scheme add yourapp --ios.

If you want to do it manually, open the project at YourApp/ios/YourApp.xcodeproj in Xcode. Select the project in sidebar and navigate to the info tab. Scroll down to "URL Types" and add one. In the new URL type, set the identifier and the URL scheme to your desired URL scheme.

Image from React Navigation documentation

To test the URI on the simulator, run the following:

npx uri-scheme open yourapp://wtv --ios

To test the URI on a real device, open Safari and type yourapp://wtv .

Both of these commands should just open to your app. We will handle the actual navigation later.

  • Note: Your universal link configuration will not work until we add a verification file to your website.

Android Basic Setup

Android uses the same term as Apple for Deep Links. However, they call Universal Links, App Links (formerly, Web Links). Full documentation on handling Android app links can be found here.

Android Deep Linking

To configure the external linking in Android, you can create a new intent in the manifest.

The easiest way to do this is with the uri-scheme package: npx uri-scheme add yourapp --android.

If you want to add it manually, open up YourApp/android/app/src/main/AndroidManifest.xml, and make the following adjustments:

<activity
android:name=".MyMapActivity"
android:exported="true"
...>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourapp" />
</intent-filter>
</activity>

When the user clicks a deep link, a disambiguation dialog might appear. This dialog allows the user to select one of multiple apps, including your app, that can handle the given deep link. Figure 2 shows the dialog after the user clicks a map link, asking whether to open the link in Maps or Chrome.

Android App Selection Dialog

This dialog won’t appear if we use App Links.

Android App Links

Android App Links, available on Android 6.0 (API level 23) and higher, are web links that use the HTTP and HTTPS schemas and contain the autoVerify attribute. This attribute allows your app to designate itself as the default handler of a given type of link. So when the user clicks on an Android App Link, your app opens immediately if it's installed—the disambiguation dialog doesn't appear.

If the user doesn’t want your app to be the default handler, they can override this behavior from the app’s settings.

The following code snippet shows an example of an Android App Link filter:

<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="myownpersonaldomain.com" />
</intent-filter>

Android Testing

Either route you used, to test the intent handling in Android, run the following:

npx uri-scheme open yourapp://wtv --android

Unlike iOS, your Android App Link filter will work at this point without any additional server config.

npx uri-scheme open https://myownpersonaldomain.com/wtv --android

Configuring Links (React Navigation)

Now we switch to the non-native part of the implementation.

All of the code below assumes you will be using the React Navigation library. If not, you will need to consult your package manager’s documentation for how they handle inbound links. For a more detailed breakdown of link configuration, check out their guide.

In this section, we will configure React Navigation to handle external links. This is necessary if you want to:

  1. Handle deep links in React Native apps on Android and iOS
  2. Enable URL integration in browser when using on web
  3. Use <Link /> or useLinkTo to navigate using paths.

The NavigationContainer accepts a linking prop that makes it easier to handle incoming links. The 2 of the most important properties you can specify in the linking prop are prefixes and config:

import { NavigationContainer } from '@react-navigation/native';const linking = {
prefixes: [
/* your linking prefixes */
],
config: {
/* configuration for matching screens with paths */
},
};
function App() {
return (
<NavigationContainer
linking={linking}
fallback={<Text>Loading...</Text>}
theme={{colors: {background: YOUR_APP_BACKGROUND}}}>
{/* content */}
</NavigationContainer>
);
}

I recommend setting your fallback to your <SplashScreen /> component. This prevents any odd rendering.

Mapping Your App

Let’s keep it simple for a fresh app.

const config = {
screens: {
Chat: 'feed/:sort',
Profile: 'user',
},
};

const linking = {
prefixes: ['https://myownpersonaldomain.com', 'yourapp://'],
config,
};

By now, the following should open the Profile page.

npx uri-scheme open yourapp://user --ios

Naming navigation flows matters! This process will take a series of guess-and-checking. If you’re like me, the naming and routing of your navigation stacks are kept to localized variables. Example: App > Main > Screen > Main. You’ll need to rename these flows to a convention easily accessible for routing.

Here is an example configuration from a production app:

const config = {
screens: {
Main: {
screens: {
App: {
initialRouteName: 'BottomTabs',
screens: {
BottomTabs: {
screens: {
Component1: 'home',
Component2: 'profile',
},
},
Event: {
path: 'events/:id',
},
},
},
},
},
NotFound: '*',
},
};

Site Associations

Universal links work through a relationship between the app’s capabilities and your website’s apple-app-site-association file for iOS and assetlinks.jsonfile for Android.

Apple App Site Association

This file must have the following properties.

  1. It must be served via HTTPS
  2. It must be served without redirects
  3. It must be at the root / of the website or inside the .well-known directory

The contents of this file are in the following form:

{
"applinks": {
"apps": [],
"details": [
{
"appID": "<TEAM_IDENTIFIER>.com.awesome.app",
"paths": [
"events/:id",
...
]
}
]
}
}
  • App Caching Error: The apple-app-site-association file is cached. It is initially downloaded on the install of the app and then is periodically re-installed. If changes to the available universal link routes appear to not be working, try deleting the app and re-installing it.
  • localhost must be served over HTTPS: Ngrok makes this simple.

Android Asset Links Verification

A Digital Asset Links JSON file must be published on your website to indicate the Android apps that are associated with the website and verify the app’s URL intents. The JSON file uses the following fields to identify associated apps:

  • package_name: The application ID declared in the app's build.gradle file.
  • sha256_cert_fingerprints: The SHA256 fingerprints of your app’s signing certificate. You can use the following command to generate the fingerprint via the Java keytool:
$ keytool -list -v -keystore my-release-key.keystore
  • This field supports multiple fingerprints, which can be used to support different versions of your app, such as debug and production builds.

The following example assetlinks.json file grants link-opening rights to a com.example Android app:

[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example",
"sha256_cert_fingerprints":
["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
}
}]

Closing Remarks

You’re now able to use both Deep Linking and Universal Linking in both iOS and Android 💥

Universal (App) Links

npx uri-scheme open https://myownpersonaldomain.com/user --androidnpx uri-scheme open https://myownpersonaldomain.com/user --ios

Deep Links

npx uri-scheme open yourapp:///user --androidnpx uri-scheme open yourapp://user --ios

Voila 💥

Bonus Content

Open Graph Content

Want open graph content to appear when your app is shared across devices? (like an iMessage between friends)

Configure your web server with the og:image and og:title content properties.

<html>
<head>
<meta property="og:image" content=…>
<meta property=“og:title" content=…>

</head>
<body>

</body>
</html>
Example og:image
<meta property="og:image" content="https://pushoflove.com/icon.png" />

Thanks for being here!

--

--

Jake Curreri
SmallWorld

Building apps @ SmallWorld. Former Fittest Man in Arkansas. Street guest on the Tonight Show. Failed Disney Channel auditionee. Elixir, RN, RoR.