Universal Links (App Links) in React Native for iOS & Android
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.
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.
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:
- Handle deep links in React Native apps on Android and iOS
- Enable URL integration in browser when using on web
- Use
<Link />
oruseLinkTo
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.json
file for Android.
Apple App Site Association
This file must have the following properties.
- It must be served via HTTPS
- It must be served without redirects
- It must be at the root
/
of the website or inside the.well-known
directory
https://example.com/apple-app-site-association
https://example.com/.well-known/apple-app-site-association
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'sbuild.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>
<meta property="og:image" content="https://pushoflove.com/icon.png" />
Thanks for being here!