How to make a complete app / site association with Universal Links

For some projects, you may build an app that will look very much like your website. Because not everyone is using an app and you want to reach a greater audience. And so to link your app and your site, you will surely use Universal Links.

You probably already have seen some tutorial to implement Universal links in your app, so it won’t be my focus here. In this article, I will walk you through the purpose of the 3 steps to implement Universal links with the exemple of 3 use cases.

After this article you will have a deeper understanding of the purpose of the different step you have to make to implement Universal link. Can you filter the links in the native part of your app? Can you tell if your app should open or not with React Navigation? You will understand everything you can do and where, in this article.

A Universal link ? What’s that ?

Universal links are deeplinks (ie, a link that can be open in an app) that starts with http:// or https://. So the purpose is to be able to open this link on an app, like any deeplink, but also to be able to open it on a browser in the case the app is not installed, like any http link. (If you want to learn more about the difference between deeplinks and universal links, here is an article for you).

The 3 steps to implement Universal links

In order to set up your universal link, in a React Native app, you will have to do 3 things:

  • Tell your app that it can handle universal links (in native iOS and Android)
  • Tell your website that it allows the app to open once you click on a universal links
  • Tell React Navigation what to do with your link

That’s what I did for a project where the app had to open the majority of the links of the website. But here is the catch : the app had to open the majority of the links, not all.

Our 3 use cases

For the rest of the article let’s consider 3 use cases for our universal links for our domain blog.bam.tech:

  • A basic link to a static page with a path /developer-news (so the link will be https://blog.bam.tech/developer-news)
  • All the links that ends with .html (for example https://blog.bam.tech/bam/is/awesome/and-so-on.html)
  • And finally a deeplink to a page we don’t want to open in the app: /contact (because we’ve not done this feature on the app yet for example)

Step 1: Native configuration: give your domain name

The first step will be your native configuration of the deeplink.

iOS

For iOS, you can just give your domain name (in our case : blog.bam.tech) and update your certificate. You can find great tutorials to do it here. There is not a lot to say about it.

Xcode

Android

There is more to say on Android. (You can continue the react-navigation tutorial here or read the documentation.)

The interesting thing is that in your AndroidManifest.xml, you can already filter some paths.

This will be the implementation to deal with all the links:

<data android:host="blog.bam.tech" android:scheme="https"/>

And if we only wanted to open the links which starts with a certain path, for example /developer/..., we would do this :

<data android:host="blog.bam.tech" 
android:scheme="https"
android:pathPrefix="/developer"/>

That’s nice ! However here, that’s not what we want to do... Let’s consider our 3 objectives:

  • The basic link: /developer-news: there is no path prefix so it doesn’t help us.
  • All the links ending with .html: unfortunately the pathPrefix options doesn’t work for this case, we can’t do something like: pathPrefix=”/*.html”
  • The link /contact that we don’t want to open: we were almost there ! If we knew all the path prefix that we want to open, we would have been able to open all of them and just exclude the /contact prefix.... However we don’t know them. So we can’t filter out /contact that way.

Recap

And that’s all we can do in the native configuration of our app. So let’s recap:

We’ve added the blog.bam.tech domain to our app.

  • On Android, it opens already any link starting with blog.bam.tech 🎉
  • On iOS, nothing happens ☹️
  • We don’t know (yet) how to open a specific page or how to filter some links ☹️

The main purpose of the native configuration is to open your app once your device detect your domain name.

 

But it isn’t enough. So let’s go to the next step!

Step 2: Website Configuration: let’s filter (on iOS)

The second steps are files you want to add to your website either at the root, or inside a /.well-known/ folder (for example https://blog.bam.tech/.well-known/apple-app-site-association). Let’s start with the disappointing one: this time Android.

Android: assetlinks.json

If you want to be fancy, you can add on your website the assetlinks.json file for your app. It is definitively something you want to do, because it’s very simple, but it won’t make a huge difference.

Having this file on your site will allow your app to directly be opened on the first time the user click on your universal link. The user will not be asked if he want to open in the app or the browser. (You can read here to generate this file).

iOS : apple-app-site-association

This one however, is great! And mandatory. To generate it have a look at this awesome article.

{
"applinks": {
"apps": [],
"details": [
{
"appID": "IOS_TEAM_ID.tech.bam.ios.debug",
"paths": ["*"]
}
]
}
}

Look at the json. Here is your dream: there is a * inside a string. It means you can use regular expression!

This is how you will filter some paths for iOS. You can either choose what url you want to open, or you can suppress some url in this file.

So in our case, paths will look like:

"paths": [ "/developer-news", 
"*.html",
"NOT /contact" ] // in this case, this condition doesn't serve, but I just wanted to show the syntaxe

Once you’ve added this file to your website, you will only be able to open all the path you have authorized on your app.

Recap

Ok, so now after adding the files on the website we have:

  • For iOS, the app open it self with the links /developer-news and *.html, however we’ve make sure that the /contact link won’t be open in the app 🎉
  • For Android, we still open every link and we didn’t find any ways not to open the /contact link ☹️

And in both cases, the app is open, but the page is not! So let’s do it!

The purpose of the apple-app-site-association for iOS is to filter the path you want to open.

For the assetlinks.json for Android, it is to authorize your app to open without asking the permission to the user.

 

 

Step 3: Configuring in React Navigation

To manage Universal links, in React Navigation, there is a great option: the linking prop in your NavigationContainer. In this object, you just need to give your domain name and then what screen you want to open for each path.

I’m not going to explain here how great it is. There’s already a whole page on the documentation of React Navigation that will do it better than I could! Instead, I am going to talk about just one option of the linking object: getStateFromPath.

The thing is, React Navigation linking object is so smart it can parse your path...

However you may not want this parsing ! In my example for the url that ends with .html I want to handle totoro.html the same way I handle my-best-friend/totoro.html. In this case, in the linking object, we have to use the function getStateFromPath.

This function will give you the whole path as an argument and you will return the state of the navigation you want to display for this path. So for our case, we will do something like that:

const linking = {
...,
getStateFromPath: (path) => {
if (path === '/developer-news') {
return {
index: 0,
routes: [{ name: 'DevelopersPage' }],
} // that's your navigation state
} else if (path.includes('.html')) {
return {
index: 0,
routes: [{name: 'HtmlPage', params: { link: path }]
}
} ...
...
},
...,
}

This is how you can really fine tune your redirection depending on your path.

Recap

Ok, so where are we on our objective for this article?

  • The basic link: /developer-news
  • All .html link: ✅
  • The /contact we don’t want to open: on iOS ✅ however on Android ❌

Unfortunately for now, there is no way to filter out links on Android ☹️. So you have to make a backup plan. The simplest thing you could do is a redirection to some page if you open a path that you haven’t manage.

But in our case, we wanted to make sure that the user would be able to go to the link, so we’ve decided to add an in-app-browser. That’s one of many solution.

So the purpose of the configuration in React Navigation is to manage what you want to do with your Universal link once you have open your app.

 

Final Thought : on a website that is not yet released

Before ending this article, I just wanted to say a word about 2 other issues that we faced.

First is that our website was not yet released when we were doing the app. Of course we had some access to it, but it was protected either with a VPN or with a password. That is not good for universal link. For your two files on the website (apple-app-site-association and assetlinks.json) should be on a public route. It’s logical: otherwise your phone won’t be able to read those files!

And finally we had an issue : when you are on your phone’s browser and you click on an universal link, the universal link will open in your browser. It is to be expected : an universal link starts with https, so it can be handle in a browser !

So to be sure that we open the app by clicking on a link in a browser, there is a great way to handle this : smart links (in our case Firebase dynamic link).

But this will be a topic for another time! (In the meantime, you can understand what it is in this article and here is the documentation to add Firebase dynamic link).