27th April 2023
Refining link behaviour
In this article, we will enhance the user experience across both Android and iOS platforms by refining link behaviour through the power of React Native 🚀.
Picture this: a user of an iOS or Android device taps on a link, perhaps via an email or text message that is tied to our specific domain. Instead of the link opening a browser window, the associated app is automatically launched taking the user to the intended page or screen.
This is the effect we’re aiming for.
A very happy user!
This concept is known on Apple devices as Universal Links and on Android they’re referred to as Deep Links.
We’ll make use of the Linking API in React Native that allows us to create custom URL schemes to set up the link to our apps. Additionally, the Linking API comes with methods to help check if an app is installed and open it if necessary, as well as handle the incoming Universal/Deep Links.
- Configured public-facing HTTPS domain with client or server implementation (no redirects)
- Existing React Native project (does not need to be published, can be a sample app)
- Android Studio
We will be focusing on applications built with native code, so if you use Expo for your React Native project there will be some differences and you should check Expo’s documentation on Linking to establish what they are. However, generally, the first step at least will still apply.
Step 1 – serve some structured JSON to establish trust
To enable linking for our React-Native application, we first need to create two files and serve them from each subdomain you want to provide links for.
So if you want to use
example.com you must serve the file from both subdomains independently. For the sake of brevity, we’ll focus on a single domain in this article.
Apple Universal Links
In order to establish trust with Apple for our application and domain, we need to host a file publicly that their CDN can scrape. Create a new file named
apple-app-site-association to be served over HTTPS from the root of the domain.
💡 Some documentation specifies adding to the
.well-knownfolder but this seems to be used primarily by legacy implementations. Apple recommends adding this to the root.
It must return a MIME type of
application/json and contain no file type, i.e. do not add
.json to the file name.
The fully qualified path of the file should be:
We have the option here to map paths manually with custom behaviour, which you can learn more about on the Apple Developers website. However, to avoid repetition or having a separate config for iOS and Android, we’ll just handle paths with our logic in React Native.
After making this file available it may take up to 48 hours for Apple’s CDN to scrape and validate when the app is first installed and a universal link is first used. This can be frustrating when it comes to troubleshooting, so ensure your config is valid and serve the file correctly.
It’s important to note that validity requests for linking are triggered on a user’s device when it is first installed. This can affect testing when linking is in fact working as intended. Just uninstall and reinstall for a sanity test — this can all be done in the simulator.
Android Deep Links
Android Deep Links use the Digital Asset Links API to establish trust between your app and websites, granting auto link-opening permissions for the specified domain. After successful verification of URL ownership, the system will automatically direct URL intents to your app.
To begin, we need to create a ‘Digital Asset Links’ JSON file to be served from each subdomain we want to provide Deep Links to.
Create a new file named
assetlinks.json to be served from this folder:
If you’re updating an existing app, verify whether you’re using Play App Signing for the app in your Play Console developer account under
Release > Setup > App Integrity. This is required to obtain the
sha256_cert_fingerprints. If you do this then the correct Digital Asset Links JSON snippet should be on this page.
Otherwise, if you’re just working with a sample app, experimenting, or don’t sign your app, the simplest way to generate the required JSON is to use Android Studio.
Tools > App Links Assistance > Step 3 - Associate website, enter your domain and application ID and choose
Generate Digital Asset Links File. This will generate the JSON you need to serve from your domain.
From here you’ll be able to link and verify that the file is structured and served correctly.
It should look something like this:
Unlike Apple’s CDN, you shouldn’t need to wait for Google to crawl the page before Deep Links become available on the user’s device — from testing, it appears the page is immediately queried for the JSON when loading a configured application.
Step 2 – Update our native code
This post was written specifically for version 0.71 of React Native. It is important to check the Linking documentation for RN for the most up-to-date information on how to implement it. This step will provide some additional context.
iOS native code
To configure our iOS application, in Xcode we first need to enable ’Associated Domains’ in the ‘Capabilities’ section of the project settings.
Add the associated domains in the format
example.com is the domain hosting the
apple-app-site-association file. Once added, you should see that your project’s entitlements file has been updated with the necessary XML.
Now we need to add a link to the React Native
LinkingIOS Node modules folder in the application’s header search paths. To add the library path, in Xcode navigate to your project’s ‘Build Settings’. From there, filter or search manually for ‘Header Search Paths’ and add the following path to your library:
With this in place, we now need to add the following code to our
AppDelegate.mm file inside of our Xcode project:
That’s it! We should now be able to start receiving Universal Links with React Native. More on this in the next step.
Android native code
Setting up our native code for Android is much simpler and we can again in Android Studio use the
App Links Assistance tool.
Inside the tool, from Step 1, Add URL intent filters and click the
Open URL Mapping Editor button. From here, add your domain as the host, leave your path settings empty and ensure your main activity is selected.
Now, in Step 2, we can use the tool to automatically add logic to our implementation in the form of an XML config update to the
It should update this with the following:
Now we’re set up we can move on to the React Native implementation. Once this is done you should return back to the
App Links Assistance tool and use Step 4 of the tool to test them on a device or simulator.
Step 3 – Get set up in React Native
Typically most projects make use of the popular
@react-navigation/native package whose
NavigationContainer component accepts a
This object configuration allows us to associate our ‘screens’ with incoming paths in a simple concise way. The documentation for ‘Configuring Links` is very comprehensive and should be followed to suit your app’s setup.
It’s worth noting that in order for universal and Deep Links to work, you must add your fully qualified domain as prefix:
Once configured in React Native’s side that should be it! You will now have a configured app for both platforms that opens the associated application when a link is tapped from the device.
React Native WebViews
If you also take advantage of React Natives WebViews or need some additional functionality based on some parameters in the URL for example then we can just use the Linking API directly.
There are 2 cases that we need to cater for this:
The user opens the link whilst the application is closed
We need to asynchronously call
getInitialURL from the Linking API in order to get the path the application was opened with.
You could implement something similar to this hook:
With this in place, we can consume this hook where needed in our React Native JS code and use it to set the URI for the WebView. We can also deconstruct the URL to get subdomains, the path or any query/hash-based properties.
💡 It’s worth noting that, by default, the built-in JS Browser-compatible
URLclass won’t work out of the box correctly with React Native. Use the
react-native-url-polyfilllibrary as a replacement.
Our final case to cover is a warm start — where the application is already open in the background.
Here we add an event listener to the Linking API that sets the updated URL into state:
This should be enough to get you up and running and working with WebViews and being able to process the opened paths in your application!
Working with simulators
At last, we have all the necessary components in place. But we still need to effectively develop and interact with simulators.
You can set up the simulator manually by inputting the information in the OS directly or by creating a webpage that contains a list of desired paths, allowing you to click on a link each time, which we should continue doing for sanity checks and testing purposes.
But this method is not practical from a development perspective and we can streamline the process a little by utilising the commands provided below:
adb shell am start -a android.intent.action.VIEW -d https://example.com
xcrun simctl openurl booted https://example.com
Ok, that’s it!
Time for a brew.
All images in this blog post (except for the featured image) were generated by Midjourney.