carlos
rafael
Publicada 10 de noviembre de 2022 · 9 min. de lectura

Adding deep linking to your expo app

Una imagen decorativa para esta página.

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'

Or you can click here

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!

Da el primer paso para hacer realidad tu proyecto.

Get in touch