Handling Deep Links in iOS/Android/React Native

Arnaud Ambroselli
6 min readApr 18, 2020

--

Universal Links, Deep Links, App Links… This is something I told my client it would it would take one or two days to setup for their app, but it actually took me a week to make it perfect. And it was not a good week, believe me…

That's why I decided to write this article. To never forget.
And to be able to make a two days invoice to my next client, but by spending half a day on the task, not a whole week.

I know a lot of people use Firebase Deep Link. I personally don't like this kind of option, if I already have the need of a backend anyway. And I like to know how everything works anyway. For me, relying too much on Firebase or AWS is… 🤢

Anyway, four parts there, sharp, direct, no extra unneeded text, no story sharing, it's an uninteresting subject, so let's close it very fast. Please.
1. Share a link
2. Format a HTTP template to handle deep links
3. Setup for handling the opening in iOS/Android/RN
4. Trust files on your server

1. Share a link

I'll put some JS code from my React Native app below, maybe it could be useful for native iOS/Android devs, I don't know, I am not such one.

The tricky part there is to have proper layout of the sharing post for all the platforms you gonna share — Facebook, WhatsApp, LinkedIn, Microsoft Teams, Messenger, iMessages, Mail, Outlook, etc.

Here below was my perfect setting, may it inspire your code :

import Share from 'react-native-share'
import { Platform } from 'react-native'
async function share({ title, url }) {
if (!url) return
// very important below: \u00A0
const subject = `This is what I am sharing: ${title}\u00A0 `
const options = Platform.select({
ios: {
activityItemSources: [
{
placeholderItem: { type: 'url', content: url },
item: {
copyToPasteBoard: {
type: 'url',
content: url
},
default: {
type: 'text',
content: `${subject}\n${url}`
},
mail: {
type: 'text',
content: `${subject}\n${url}`
},
},
subject: {
default: subject,
},
},
],
},
default: {
title,
subject,
message: subject,
url,
},
})
await Share.open(options)
.then(({ app }) => {
console.log('app', app) // this tells you what platform was chosen
return true
})
.catch((err) => {
if (err) console.log(err)
return true
})
}

For my experience, everything is important in the activityItemSources object : even if default and mail are the same, they were needed in my case.

So be it.

2. Format a HTTP template to handle deep links

Keep in mind that :

  • Facebook App Links, supposed to be working with a whole bunch of al meta tags, didn't work at all for me. This explains the hacky JS script below. I spent at least half a day on this particular issue. Stephen Radfors saved me.
  • Facebook and Messenger : same same, same behaviour. I guess Instagram also, although I didn't have to try Insta because you can only share pictures there, which was not my case. Not WhatsApp
  • WhatsApp works great, except that sometimes, the url picture doesn't show up. I can't explain why. Sometimes yes, sometimes no. My advice : don't spend too much time on this mistery, it's not worth it. Even YouTube links sometimes doesn't show preview..
  • LinkedIn, Pinterest : the deep link opening in the app doesn't work at all. I think that's not a feature they have implemented. The only thing you can do is opening the stores, no more. Shame on them. If LinkedIn CTO or Pinterest CTO read this : shame on your choices. Change them.

That being said, here is my final template :

<html>
<head>
<meta charset="UTF-8">
<title>{ELEMENT_TITLE}</title>
<meta name="description" content="Find this in my app">
<meta property="og:title" content="{ELEMENT_TITLE}" />
<meta property="og:url" content="{ELEMENT_URL}" />
<meta property="og:description" content="Find this in my app">
<meta property="og:image" content="{ELEMENT_PICTURE}" >
<meta property="og:type" content="article" />
<meta property="fb:app_id" content="{FACEBOOK_APP_ID}" />
<meta property="al:ios:url" content="myapp://element/{ELEMENT_ID}" />
<meta property="al:ios:app_store_id" content="{APP_STORE_ID}" />
<meta property="al:ios:app_name" content="MyApp" />
<meta property="al:android:url" content="myapp://element/{ELEMENT_ID}" />
<meta property="al:android:app_name" content="MyApp" />
<meta property="al:android:package" content="{ANDROID_PACKAGE_NAME}" />
<meta property="al:web:url" content="{ELEMENT_OUTSIDE_URL}" />
<meta property="al:web:should_fallback" content="false" />
<script>
function redirect() {
var userAgent = navigator.userAgent || navigator.vendor || window.opera;
var ios = /iPad|iPhone|iPod/.test(userAgent) && !window.MSStream;
if (ios) {
window.location = myapp://element/{ELEMENT_ID};
window.setTimeout(() => {
window.location.replace('{APP_STORE_URL}');
}, 25)
return
}
var android = /android/i.test(userAgent);
if (android) {
window.location = myapp://element/{ELEMENT_ID};
window.setTimeout(() => {
window.location.replace('{PLAY_STORE_URL}');
}, 25)
return
}
window.location.replace('{ELEMENT_OUTSIDE_URL}')
}
redirect()
</script>
</head>
<body>
</body>
</html>

I think the file speaks for itself, except for those lines

window.location = myapp://element/{ELEMENT_ID};
window.setTimeout(() => {
window.location.replace('{APP_STORE_URL}');
}, 25)

This is for Facebook : as all the al tags don't work, this is the hack to make deep links work within Facebook. And to all apps that are nice enough to handle custom scheme urls like myapp://. Not LinkedIn.
If the app succeed to handle the custom scheme url, it will open your app, if not, 25ms later it will open the store.

3. Setup for handling the opening in iOS/Android/RN

So from above you understood that custom scheme is compulsory to make deep linking work. No custom scheme, no deep links opening on Facebook apps.

The settings below will allow you to open this kind of links:

  • httpS://share.myapp.com/anything/you/want/there
  • myapp://anything/you/want/there

Setup for Android

Add this activity to your/android/app/src/main/AnrdoidManifest.xml

<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize"
android:exported="true"
android:launchMode="singleTop">
<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="https"
android:host="share.myapp.com" />
</intent-filter>
<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="myapp" />
</intent-filter>
</activity>

Setup for iOS

For the Universal Link, the one in https scheme, open your .xworkspace in XCode, then from your Project Navigator go to Signing & Capabilities, add a Capability Associated Domains, and add applinks:share.myapp.com . Add as many applinks as you wish.

You can also directly go to /ios/myapp/myapp.entitlements and add this key/array combination:

<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:share.myapp.com</string>
</array>

Then in the Info tab, scroll to bottom to find the URL Types part, and add one where you only put myapp to the field URL Schemes.

You can also directly go to /ios/myapp/Info.plist and add this key/array combination:

<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>

Opening in React Native

The setup above makes the app open when clicking on the links. This part is to handle the opening, i.e. read the link, in React Native. No shame on you if you skip this part.

So, really easy to setup :

import React from 'react'
import { Linking } from 'react-native'
class MyComponent extends React.Component {
componentDidMount() {
const initialUrl = await Linking.getInitialURL()
if (initialUrl) this.handleLinkingUrl(initialUrl)
Linking.addEventListener('url', ({ url }) => {
this.handleLinkingUrl(url)
})
}
handleLinkingUrl = url => {
...
}
render() {
...
}
}

4. Trust files on your server

Of course, nothing will work there if you don't put trust files on your server.

Android setup

For Android, have a look there : https://developer.android.com/training/app-links/verify-site-associations#web-assoc. You can actually read the whole article, it's quite good and well explained.

I have nothing to say more there, except that you can also read this if you want to : https://developer.android.com/training/app-links/deep-linking. I like the testing parts…

Apple setup

Same, everything is quite well explained there : https://developer.apple.com/documentation/safariservices/supporting_associated_domains_in_your_app#3001215

TLDR, A basic file ASAA file would be stored on https://share.myapp.com/.well-know/apple-app-site-association

{
"applinks": {
"apps": [],
"details": [
{
"appID": "{TEAM_ID}.{BUNDLE_ID}",
"paths": [
"NOT /_/*",
"/*"
]
}
]
}
}

I think that's it ! If I missed something, please tell me in the comments below.

References

--

--