How to handle Deep Links in a React Native app

Jan 8, 2020

8 min read

#react-native

cover

Originally published at Jscrambler

Deep Linking is a technique in which with a given URL or resource, a specific page or screen in mobile gets open. Navigating to this specific page or screen which can be under a series of hierarchical pages, hence the term "deep", is called deep linking.

In this tutorial, let us try to mimic a react-native demo app that opens a specific page based on the URI provided from an external source. To handle deep links, I am going to use an optimum solution provided by react-navigation library.

You can find the complete code for the tutorial at this GitHub repo.

Configure react-navigation in a React Native app

🔗

To start, create a new React Native project by running the following command:

GNU Bash icon

react-native init rnDeepLinkingDemo
cd rnDeepLinkingDemo

To be able to support Deep linking via the navigation, add the required npm dependencies. Once the project directory has been generated from the above command, navigate inside the project folder from your terminal and install the following dependencies.

GNU Bash icon

yarn add react-navigation react-navigation-stack
react-native-gesture-handler react-native-reanimated
react-native-screens@1.0.0-alpha.23

The next step is to link all the libraries you just installed. I am using React Native version greater than 0.60.x. If you are using a lower version of React Native, please follow the instructions to link these libraries from here.

Only for the iOS devices, you just have to run the following set of commands.

GNU Bash icon

cd ios
pod install

For the Android devices, add the following lines to the android/app/build.gradle file under dependencies section:


1implementation 'androidx.appcompat:appcompat:1.1.
20-rc01'
3implementation 'androidx.
4swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'

Then open android/app/src/main/java/com/rndeeplinkdemo/MainActivity.java file and add the following snippet:


1// Add this with other import statements
2import com.facebook.react.ReactActivityDelegate;
3import com.facebook.react.ReactRootView;
4import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
5// Add this inside "class MainActivity"
6@Override
7protected ReactActivityDelegate createReactActivityDelegate() {
8return new ReactActivityDelegate(this, getMainComponentName()) {
9@Override
10protected ReactRootView createRootView() {
11return new RNGestureHandlerEnabledRootView(MainActivity.this);
12}
13};
14}

Create a Home & Details Screen

🔗

I am going to create all the screens for the rest of this tutorial inside the directory src/screens. To start with the Home screen, create a new file Home.js. inside the aforementioned path.

This screen is going to render a list of users from an array of mock data from a placeholder API using a FlatList component. Each user is going to be wrapped inside a TouchableOpacity. The reason being, when an end-user press a user name from the list, this is going to contain the logic of navigating from Home screen to Details screen (which we will add later).

JavaScript icon

1import React, { useState, useEffect } from 'react';
2import { View, Text, FlatList, TouchableOpacity } from 'react-native';
3
4function Home() {
5 const [data, setData] = useState([]);
6
7 useEffect(() => {
8 fetch('https://jsonplaceholder.typicode.com/users')
9 .then(res => res.json())
10 .then(res => {
11 setData(res);
12 })
13 .catch(error => {
14 console.log(error);
15 });
16 }, []);
17
18 const Separator = () => (
19 <View
20 style={{
21 borderBottomColor: '#d3d3d3',
22 borderBottomWidth: 1,
23 marginTop: 10,
24 marginBottom: 10
25 }}
26 />
27 );
28
29 return (
30 <View style={{ flex: 1 }}>
31 <View style={{ paddingHorizontal: 20, paddingVertical: 20 }}>
32 <FlatList
33 data={data}
34 keyExtractor={item => item.id}
35 ItemSeparatorComponent={Separator}
36 renderItem={({ item }) => (
37 <TouchableOpacity onPress={() => alert('Nav to details screen')}>
38 <Text style={{ fontSize: 24 }}>{item.name}</Text>
39 </TouchableOpacity>
40 )}
41 />
42 </View>
43 </View>
44 );
45}
46
47export default Home;

For the details screen, for now, let us just display a text string. Create a new file called Details.js.

JavaScript icon

1import React from 'react';
2import { View, Text } from 'react-native';
3
4function Details() {
5 return (
6 <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
7 <Text>Deep Link Screen</Text>
8 </View>
9 );
10}
11export default Details;

Configure Deep Linking in React Navigation

🔗

To navigate from Home to Details screen, we need Stack Navigator from react-navigation. Create a new file called index.js inside src/navigation directory and import the following statements.

JavaScript icon

1import React from 'react';
2import { createAppContainer, createSwitchNavigator } from 'react-navigation';
3import { createStackNavigator } from 'react-navigation-stack';
4import Home from '../screens/Home';
5import Details from '../screens/Details';

Create a stack navigator with Home as the initial screen.

JavaScript icon

1const MainApp = createStackNavigator({
2 Home: {
3 screen: Home,
4 navigationOptions: {
5 headerTitle: 'Home'
6 }
7 },
8 Details: {
9 screen: Details,
10 navigationOptions: {
11 headerTitle: 'Details'
12 }
13 }
14});

To enable deep linking the current app requires an identifier to recognize the URI path from the external source to the screen of the app.

The library react-navigation provides path attribute for this. It tells the router relative path to match against the URL. Re-configure both the routes as following:

JavaScript icon

1const MainApp = createStackNavigator({
2 Home: {
3 screen: Home,
4 navigationOptions: {
5 headerTitle: 'Home'
6 },
7 path: 'home'
8 },
9 Details: {
10 screen: Details,
11 navigationOptions: {
12 headerTitle: 'Details'
13 },
14 path: 'details/:userId'
15 }
16});

In the above snippet, the dynamic variable specified by :userId is passed to details/. This is going to allow the app to accept a dynamic value such as details/1234.

Next, add the configuration to the navigation to extract the path from the incoming URL from the external resource. This is done by uriPrefix. Add the following code snippet at the end of the file.

JavaScript icon

1export default () => {
2 const prefix = 'myapp://';
3 return <AppContainer uriPrefix={prefix} />;
4};

Import this navigation module inside App.js file for it to work.

JavaScript icon

1import React from 'react';
2import AppContainer from './src/navigation';
3
4const App = () => {
5 return <AppContainer />;
6};
7
8export default App;

Configure URI scheme for native IOS apps

🔗

To make this work, you have to configure the native iOS and Android app to open URLs based on the prefix myapp://.

For iOS devices, open Open ios/rnDeepLinkDemo/AppDelegate.m file and add the following.


1// Add the header at the top of the file:
2#import <React/RCTLinkingManager.h>
3// Add this above the `@end`:
4- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url
5 options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
6{
7 return [RCTLinkingManager application:app openURL:url options:options];
8}

Open the ios/rnDeepLinkingDemo.xcodeproj in the Xcode app and select the app from the left navigation bar.

jss1

Open the Info tab.

jss2

Next, go to the URL Types.

jss3

Click the + button and in identifier as well as URL schemes add myapp.

jss4

Rebuild the react native binaries by running react-native run-ios.

For android users, you have to configure the external linking as well. Open /android/app/src/main/AndroidManifest.xml and set the value of launchMode to singleTask. Also, add a new intent-filter.


1<activity
2 android:name=".MainActivity"
3 <!--set the below value-->
4 android:launchMode="singleTask">
5 <intent-filter>
6 <action android:name="android.intent.action.MAIN" />
7 <category android:name="android.intent.category.LAUNCHER" />
8 </intent-filter>
9 <!--Add the following-->
10
11 <intent-filter>
12 <action android:name="android.intent.action.VIEW" />
13 <category android:name="android.intent.category.DEFAULT" />
14 <category android:name="android.intent.category.BROWSABLE" />
15 <data android:scheme="mychat" />
16 </intent-filter>
17</activity>

Testing the app

🔗

Before you run the app on your choice of platform, make sure to re-build it using the specific command for the mobile OS as below:

GNU Bash icon

# ios
react-native run-ios
# android
react-native run-android

The Home screen of the app is going to be like below.

jss5

Open a web browser in your simulator device, and run the URL myapp://home. It is going to ask you to whether open the external URI in the app associated as shown below.

jss6

Next, try entering the URL myapp://details/1 and see what happens.

jss7

Notice in the above demo that on visiting the last-mentioned URI, it opens the details screen in the app but fails to show details of the specific user. Why? Because we have to add the business logic for it to recognize the dynamic parameters based on the external source.

Access dynamic parameters in a route

🔗

To display information for each user when visiting Details screen, you have to pass the value of each item using navigation parameters. Open Home.js file and replace the value onPress prop on TouchableOpacity as shown below.

JavaScript icon

1<TouchableOpacity onPress={() => navigation.navigate('Details', { item })}>
2 {/* rest of the code remains same*/}
3</TouchableOpacity>

Next, open Details.js. To fetch the data from the placeholder API on initial render, let us use useEffect hook from React. This hook is going to behave like a good old lifecycle method componentDidMount(). It also allows that if the full item object is passed or not. If not, just grab the userId and request the API. Start by modifying the following import statement.

JavaScript icon

1import React, { useState, useEffect } from 'react';

Then, define a state variable data to store the incoming user information. Also, modify the contents of return in this component screen.

JavaScript icon

1function Details({ navigation }) {
2 const [data, setData] = useState([]);
3
4 useEffect(() => {
5 const item = navigation.getParam('item', {});
6
7 if (Object.keys(item).length === 0) {
8 const userId = navigation.getParam('userId', 1);
9 fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
10 .then(res => res.json())
11 .then(res => {
12 const data = [];
13
14 Object.keys(res).forEach(key => {
15 data.push({ key, value: `${res[key]}` });
16 });
17
18 setData(data);
19 });
20 } else {
21 const data = [];
22
23 Object.keys(item).forEach(key => {
24 data.push({ key, value: `${item[key]}` });
25 });
26
27 setData(data);
28 }
29 }, []);
30
31 return (
32 <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
33 {data.map(data => (
34 <Text
35 style={{ fontSize: 20 }}
36 key={data.key}
37 >{`${data.key}: ${data.value}`}</Text>
38 ))}
39 </View>
40 );
41}

Here is the output you are going to get from the above modifications.

jss8

Now, let us try to open a user's detail based on available ids from an external source such as a web browser.

jss9

Conclusion

🔗

That's it. You have now a complete demo of a React Native app that handles deep linking using react-navigation.


Enjoyed this post? Sign up to my newsletter!

A periodic update about my recent blog posts and tutorials. Join 1300+ devs!

No spam, unsubscribe at any time!

Subscribe on Revue

HomeBlogNewsletterAbout meSpeakingEmail

©  2019-2022 Aman Mittal · All Rights Reserved