Adding deep linking to your expo app
Deep linking is one of the most desired functionalities when building an app. Sadly, it's also one of the hardest to get right on the first try. Whether you want to use custom scheme URLs, universal links, or app links, we've got you covered.
deep linking expo expo-linking
Installing stuff
Using your package manager, install the expo-linking
package, if you use yarn
the command is the following:
yarn add expo-linking
Handling URLs
The first you should do is adding the listeners to the top-level component of your application.
To do that, you'll make use of the hook useURL
from the expo-linking
package combined with a useEffect
hook that listens on the url changes.
Starting with the base create-expo-app
application, the file App.js
would look something like the following. Note that App
is the top-level component of the application.
import * as Linking from 'expo-linking';
import { useEffect } from 'react';
export default function App() {
const url = Linking.useURL();
useEffect(() => {
// Do something with url
console.log(url);
}, [url]);
// Rest of App component...
}
Parsing URLs
In this case, once you open a deeplink url it would be simply logged. You can write a handleURL
function to isolate that logic.
// App.js > App()
const handleURL = (url) => {
const { hostname, path, queryParams } = Linking.parse(url);
if (path === 'alert') {
alert(queryParams.str);
} else {
console.log(path, queryParams);
}
}
useEffect(() => {
// Do something with URL
if (url) {
handleURL(url);
} else {
console.log('No URL');
}
}, [url])
Now, handleURL
will parse the given url, and if the path is alert
it will alert the value of the str
param. For example, opening /alert?str='hello world'
would call alert('hello world')
.
Also, useEffect
will be first called with url not initialized when opening the app, so you have to manage that case.
Opening URLs
Now that the handlers are all set up it's time to test them. In order to open a URL in your app, you need a URL to open in the first place!
This URL is different depending on the environment the app is running, so hopefully the next cases will cover them up for you.
Opening URLs in Expo Go
Probably, you are developing your app with Expo Go. To open a URL like /alert?str='hello world''
you should open the following:
exp://127.0.0.1:19000/--/alert?str='hello world'
To open the Expo Go app, the prefix exp://
is needed, then it follows the environment you are running the development build.
exp://[address:port]/--/[url]
Anyways, you can find it below the QR Code in the development console when pressing c
. It will show a message like: "Metro waiting on exp://127.0.0.1:19000", just use the address there and form your URL appeding /--/[yourURL]
after that.
Opening URLs in your standalone App
Once the app is built, the URL that triggers the app to open will no longer be an expo exp://
link. It's time for you to decide your own scheme.
For example, if you want to open links starting with dlinking://
such as dlinking://alert?str='hello world'
you have to set your app scheme to dlinking
. To do that, simply modify your app config in app.json
:
{
"expo": {
"scheme": "dlinking",
// Rest of app.json
}
}
Once your app is built, it will try to open every URL starting with dlinking://
Deep linking
There are some cases in which handling URLs with a custom scheme is not enough. For example, you might want to open a regular HTTPS link directly in your app, allowing the user to open them in a web browser if the app is not installed as a fallback. This type of links are called deep links in Android and universal links in iOS.
Deep links in Android
To configure an application to open an HTTPS link, you just have to add some fields to your app configuration in app.json
as we did when adding the custom scheme
.
For example, if we want to open the links of the form
https://negativeepsilon.com/deeplinking/
such as
https://negativeepsilon.com/deeplinking/alert?str='hello world'
We would drop this under the android
value of our app.json
{
"expo": {
"android": {
"intentFilters": [
{
"action": "VIEW",
"data": [
{
"scheme": "https",
"host": "negativeepsilon.com",
"pathPrefix": "/deeplinking"
}
],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}
}
}
Simply change the host
and pathPrefix
to your values and build your app again. Now, when you click links like https://[host][pathPrefix]
you will get prompted to select where to open the URL.
To open the URL directly in the app (without being prompted), you have to configure Android App Links.
Configuring Android App Links
Add a /.well-know/assetlinks.json
In order to make Android open the links directly in your application, the system needs to verify if it's the host intention to allow that. To do that, it will query https://[host]/.well-known/assetlinks.json
with the app ID upon app installation or update.
To make this JSON you need your app package_name
, and the sha256_cert_fingerprints
. If your app is uploaded to Google Play Console, you can go to Release > Setup > App integrity > App Signing
and copy and paste the Digital Asset Links JSON to your assetlinks.json
and publish it under the path specified before. If your app is not uploaded you can find the sha256
fingerprint either with a package inspector or in within the information expo.dev provides.
You can generate your assetlinks.json
file or check if an available one is valid with this official Google Developer tool.
Set autoVerify to true
As last step, add set autoVerify
to true
in the corresponding intentFilter
under app.json
configuration:
{
"expo": {
"android": {
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [
{
"scheme": "https",
"host": "negativeepsilon.com",
"pathPrefix": "/deeplinking"
}
],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}
}
}
If running into some unexpected problem, don't forget to check the official docs on asset links.
Universal links in iOS
To implement universal links to your app in iOS, you will have to do a similar process as you did configuring Android App Links. To do this, you have to serve an Apple App Site Association (AASA) file from your server.
The AASA file has to be served from /.well-known/apple-app-site-association
with no extension. This AASA contains a JSON that specifies the necessary information to make the verification. Following our example, and assuming that the appId
is XXXXXXXXXX.com.negativeepsilon.deeplinking
, our AASA file contains:
{
"applinks": {
"apps": [], // This have to be included
"details": [
{
"appID": "XXXXXXXXXX.negativeepsilon.deeplinking",
"paths": ["/deeplinking/*"]
}
]
}
}
Your appId
is your 10-digit teamID which you can find in Apple Developer Acount, followed by its packageName
as [teamId].[packageName]
Finally, you can check your AASA with the AASA Validator. Also, if you encounter any problem go to the official Apple Documentation on AASA.
(Optional) Usage along navigation
One of the most common uses of deep linking is to navigate to a specific screen withing your application. You can check how to add navigation to your expo app with this post.
Adding react-navigation to your expo app
We will modify the result of that post to handle deep links with navigation.
En muchos casos vamos a querer navegar a una pantalla específica cuando el usuario pulsa en un enlace.
To navigate to a specific screen when the user open a link with the app, you will have to access to the navigate prop
of the navigator. But that prop
is only availabe within the NavigatorContainer
, so you have to pass it to the parent through references.
In case you don't know much about references, think of them as component identificators that let you call inner methods from its parent.
If you want a more exhaustive explanation you might wait to our references post or visit the official docs.
So, we create the reference we are going to pass the navigator to use its functions despite calling them within the parent.
const nav = useRef();
and we add it to our navigator
return (
<View style={styles.container}>
<Navigator ref={nav} />
</View>
)
Now, we have to add a little modification to src/navigations/index.js
to join this reference to our navigator.
import React, { forwardRef } from 'react';
import { NavigationContainer} from '@react-navigation/native';
import { MainStackNavigator } from './MainStackNavigator';
const RootNavigator = forwardRef((props, ref) => {
return (
<NavigationContainer ref={ref} {...props}>
<MainStackNavigator />
</NavigationContainer>
)
})
export default RootNavigator;
Using the function forwardRef
from react
, you can pass a child component a reference from the parent, so you can use it's functions in the parent component.
Then, you can use the function navigate
with nav.current.navigate
instead of the usual navigation.navigate
wherever you declared const nav = useRef()
. For example:
// App.js > App()
const nav = useRef();
const handleURL = (url) => {
const { hostname, path, queryParams } = Linking.parse(url);
if (path === 'alert') {
alert(queryParams.str);
} else if (path === 'navigate') {
nav.current.navigate('SomeScreen');
} else {
console.log(path, queryParams);
}
}
return (
<View>
<Navigator ref={nav}>
</View>
)
And that's all for now. Hope it was helpful as configuring linking it's one of the hardest things to do for the first time. Also, make sure you check the documentation if you encounter any issue.
Thanks for reading!