January 8, 2020

How To Handle Deep Linking in a React Native App

by Aman Mittal

How To Handle Deep Linking in a React Native App

Deep Linking is a technique in which a given URL or resource is used to open a specific page or screen on mobile. So, instead of just launching the app on mobile, a deep link can lead a user to a specific page within the app, providing a better experience. This specific page or screen may reside under a series of hierarchical pages, hence the term "deep" in deep linking.

As a user, you probably have experienced deep linking when opening a link to a product in a browser. If you have the app of that shop installed, it may use a deep link to open the app on that product’s page.

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 the 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:

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.

yarn add react-navigation react-navigation-stack react-native-gesture-handler react-native-reanimated [email protected]

The next step is to link all the libraries you just installed. I am using a 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.

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

cd ios
pod install

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

implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'

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

// Add this with other import statements
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
// Add this inside "class MainActivity"
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName()) {
        @Override
        protected ReactRootView createRootView() {
            return new RNGestureHandlerEnabledRootView(MainActivity.this);
        }
    };
}

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 presses a username from the list, this is going to contain the logic for navigating from the Home screen to the Details screen (which we will add later).

import React, { useState, useEffect } from 'react'
import { View, Text, FlatList, TouchableOpacity } from 'react-native'

function Home() {
  const [data, setData] = useState([])

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users')
      .then(res => res.json())
      .then(res => {
        setData(res)
      })
      .catch(error => {
        console.log(error)
      })
  }, [])

  const Separator = () => (
    <View
      style={{
        borderBottomColor: '#d3d3d3',
        borderBottomWidth: 1,
        marginTop: 10,
        marginBottom: 10
      }}
    />
  )

  return (
    <View style={{ flex: 1 }}>
      <View style={{ paddingHorizontal: 20, paddingVertical: 20 }}>
        <FlatList
          data={data}
          keyExtractor={item => item.id}
          ItemSeparatorComponent={Separator}
          renderItem={({ item }) => (
            <TouchableOpacity onPress={() => alert('Nav to details screen')}>
              <Text style={{ fontSize: 24 }}>{item.name}</Text>
            </TouchableOpacity>
          )}
        />
      </View>
    </View>
  )
}

export default Home

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

import React from 'react'
import { View, Text } from 'react-native'

function Details() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Deep Link Screen</Text>
    </View>
  )
}
export 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 the src/navigation directory and import the following statements.

import React from 'react'
import { createAppContainer, createSwitchNavigator } from 'react-navigation'
import { createStackNavigator } from 'react-navigation-stack'
import Home from '../screens/Home'
import Details from '../screens/Details'

Create a stack navigator with Home as the initial screen.

const MainApp = createStackNavigator({
  Home: {
    screen: Home,
    navigationOptions: {
      headerTitle: 'Home'
    }
  },
  Details: {
    screen: Details,
    navigationOptions: {
      headerTitle: 'Details'
    }
  }
})

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 a path attribute for this. It tells the router relative path to match against the URL. Re-configure both the routes as follows:

const MainApp = createStackNavigator({
  Home: {
    screen: Home,
    navigationOptions: {
      headerTitle: 'Home'
    },
    path: 'home'
  },
  Details: {
    screen: Details,
    navigationOptions: {
      headerTitle: 'Details'
    },
    path: 'details/:userId'
  }
})

Protect your Code with Jscrambler

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:

export default () => {
  const prefix = 'myapp://'
  return <AppContainer uriPrefix={prefix} />
}

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

import React from 'react'
import AppContainer from './src/navigation'

const App = () => {
  return <AppContainer />
}

export 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 the ios/rnDeepLinkDemo/AppDelegate.m file and add the following.

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

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

React Native Deep Linking

Open the Info tab.

React Native Deep Linking

Next, go to the URL Types.

React Native Deep Linking

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

React Native Deep Linking

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:

<activity
 android:name=".MainActivity"
 <!--set the below value-->
 android:launchMode="singleTask">
 <intent-filter>
 <action android:name="android.intent.action.MAIN" />
 <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter>
 <!--Add the following-->

 <intent-filter>
 <action android:name="android.intent.action.VIEW" />
 <category android:name="android.intent.category.DEFAULT" />
 <category android:name="android.intent.category.BROWSABLE" />
 <data android:scheme="mychat" />
 </intent-filter>
</activity>

Testing The App

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

# ios
react-native run-ios

# android
react-native run-android

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

React Native Deep Linking

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.

React Native Deep Linking

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

React Native Deep Linking

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 the Details screen, you have to pass the value of each item using navigation parameters. Open Home.js and replace the value onPress prop on TouchableOpacity as shown below.

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

Next, open Details.js. To fetch the data from the placeholder API on initial render, let us use the 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.

import 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.

function Details({ navigation }) {
  const [data, setData] = useState([])

  useEffect(() => {
    const item = navigation.getParam('item', {})

    if (Object.keys(item).length === 0) {
      const userId = navigation.getParam('userId', 1)
      fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
        .then(res => res.json())
        .then(res => {
          const data = []

          Object.keys(res).forEach(key => {
            data.push({ key, value: `${res[key]}` })
          })

          setData(data)
        })
    } else {
      const data = []

      Object.keys(item).forEach(key => {
        data.push({ key, value: `${item[key]}` })
      })

      setData(data)
    }
  }, [])

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      {data.map(data => (
        <Text
          style={{ fontSize: 20 }}
          key={data.key}>{`${data.key}: ${data.value}`}</Text>
      ))}
    </View>
  )
}

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

React Native Deep Linking

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

React Native Deep Linking

Conclusion

That's it!

You have now finished a complete demo of a React Native app that handles deep linking using react-navigation.

Deep linking can bring significant improvements to the user experience of your mobile apps and enable search engines to provide context-sensitive searches and results.

Hopefully, this guide will help you achieve great results in your own app.

Lastly, don't forget to pay special attention if you're developing commercial React Native apps that contain sensitive logic. You can protect them against code theft, tampering, and reverse engineering by following our guide.