Understanding deep links

In this post I would like to walk you through how to add a support of deep linking to your React Native application. I would like to tell you about a react-native-deep-link, a package written by me, created to reduce amount of code needed to implement this functionality. The package allows you to concentrate on writing application logic.

In this tutorial I am going to write a simple “Colors” application using react-native-deep-link package. The app will be able to handle incoming urls, containing the name of a color, and navigate a user to a screen of the corresponding color.

Before we start I would like to explain what deep linking is. Mobile app deep links (also known simply as “deep links”) point to content inside an app. It is useful sometimes to navigate user to a certain screen on app start rather than open a home screen. A general structure of a deep link is the following schemename://path?query_string. Scheme is an equivalent of a protocol in web. It is essential to choose a unique scheme name according to your brand to avoid conflicting schemes across different applications. Real deep links examples: twitter://timeline, fb://profile.

In this application I will be using the next url format example://colors/<color>?textColor=<text_color>, where color and textColor are parameters we will use to configure the appearance of a color screen, color sets a background color of the screen, textColor sets the color of the text with a color name at the center of the screen. I am going to write code for Android platform, but you can run the same code on iOS device, the difference will be shown in the configuration section.

The final code is available here.

Implementing basic components

First of all, we should create a new react-native project.

react-native init example

Then we need to install some packages.

npm i --save redux react-redux react-navigation react-native-deep-link@0.0.3

We will use react-navigation package to add navigation to our application. First of all, we need to add HomeScreen.js file to the project. It will be an initial screen of the application.

// HomeScreen.js import React from 'react';

import { View, Text, StyleSheet } from 'react-native';



export default function HomeScreen() {

return (

<View style={styles.container}>

<Text style={styles.text}>

Welcome to colors app!

</Text>

</View>

);

}



const styles = StyleSheet.create({

container: {

flex: 1,

alignItems: 'stretch'

},

text: {

fontSize: 24,

marginBottom: 18

}

});

In our application we want to have a support of deep linking. When we receive a url we want to navigate user to a separate screen with an appropriate background and text at the center of the screen. Let’s add this screen to our project, name this file ColorScreen.js.

// ColorScreen.js import React, { Component } from 'react';

import { View, Text, StyleSheet } from 'react-native';



const DEFAULT_BACKGROUND_COLOR = 'white';

const DEFAULT_TEXT_COLOR = 'black';



export default class ColorScreen extends Component {

static navigationOptions = ({ navigation: { state: { params: { color } } } }) => {

return {

title: color

}

};



render() {

const { navigation: { state: { params: { color, textColor }}}} = this.props;



return (

<View style={[styles.container, { backgroundColor: color || DEFAULT_BACKGROUND_COLOR }]}>

<Text style={[styles.text, { color: textColor || DEFAULT_TEXT_COLOR }]}>

{color.toUpperCase()}

</Text>

</View>

);

}

}



const styles = StyleSheet.create({

container: {

flex: 1,

justifyContent: 'center',

alignItems: 'center'

},

text: {

fontSize: 54

}

});

We will use navigation state parameters provided by react-navigation package to pass information about the color and text color to the screen.

// ColorScreen.js ...

const { navigation: { state: { params: { color, textColor }}}} = this.props;

...

So, now we have two screens and we want to add navigation to our project. We need to create a root navigator. Add AppNavigator.js file to the project.

// AppNavigator.js import { StackNavigator } from 'react-navigation';

import HomeScreen from './HomeScreen';

import ColorScreen from './ColorScreen';



const AppNavigator = StackNavigator({

Home: {

screen: HomeScreen,

navigationOptions: {

title: 'Home'

}

},

Color: {

screen: ColorScreen

}

});



export const AppRouter = AppNavigator.router;

export default AppNavigator;

We will add navigation state to redux store. Add reducer.js file to the project with the following code.

// reducer.js import { AppRouter } from './AppNavigator';

import { combineReducers } from 'redux';



const initialState = AppRouter.getStateForAction(AppRouter.getActionForPathAndParams('Home'));



const navReducer = (state = initialState, action) => {

const nextState = AppRouter.getStateForAction(action, state);



return nextState || state;

};



export default combineReducers({

nav: navReducer

});

We can use created navigator component in our project. Open App.js file created by react-native-cli and replace the content with the following code.

// App.js import React from 'react';

import { addNavigationHelpers } from 'react-navigation';

import { connect } from 'react-redux';

import AppNavigator from './AppNavigator';



function App({ dispatch, nav }) {

return (

<AppNavigator navigation={addNavigationHelpers({

dispatch,

state: nav

})}/>

);

}



const mapStateToProps = (state) => ({

nav: state.nav

});



export default connect(mapStateToProps)(App);

We have added a reducer, but we have not created a Redux store for the application. Add the follwoing code to index.js file created by react-native-cli.

// index.js import React from 'react';

import { AppRegistry } from 'react-native';

import { Provider } from 'react-redux';

import { createStore } from 'redux';

import App from './App';

import reducer from './reducer';



const store = createStore(reducer);



function Root() {

return (

<Provider store={store}>

<App />

</Provider>

);

}



AppRegistry.registerComponent('example', () => Root);

Now we can launch the application. But we have not added support of deep linking yet.

Adding deep linking

We are going to use react-native-deep-link package to add support of deep linking to the application.

But do not worry, it is extremely easy, the package will set everything up for us. All we need to do is import a createDeepLinkingHandler function from react-native-deep-link and pass an array of scheme configs to the function. The function will return a higher-order component, we will use it in our App component later. Add file with the following code to the application.

// withDeepLinking.js import { createDeepLinkingHandler } from 'react-native-deep-link';

import { NavigationActions } from 'react-navigation';



const handleColorScreenDeepLink = ({ params: { color }, query: { textColor }}) => ({ dispatch }) => {

dispatch(NavigationActions.navigate({

routeName: 'Color',

params: { color, textColor }

}));

}



export default withDeepLinking = createDeepLinkingHandler([{

name: 'example:',

routes: [{

name: '/colors/:color',

callback: handleColorScreenDeepLink

}]

}]);

createDeepLinkingHandler takes an array of objects. Each object should be a scheme config, which includes the name of scheme and array of route configs. Each route config has properties name and callback . Callback is a function which will be invoked in case of successful mapping of the deep link url to a route expression specified in property name. Route callback is a higher-order function which receives the result of url parsing and returns a function. This returned function receives component props. A result of url parsing is an object with the next set of properties:

{

scheme: 'example:',

route: '/colors/:color',

query: {}, // Query string parameters

params: {} // Url parameters

}

In our application we defined one route ‘/colors/:color’ and a callback to handle incoming urls. In this callback we receive color and textColor parameters using params and query provided by react-native-deep-link package.

So ,all we need to do now is to pass App component to withDeepLinking higher-order component.

// App.js ...

import withDeepLinking from './withDeepLinking'; ...

// Definition of App component

... export default connect(mapStateToProps)(withDeepLinking(App));

That is all. Now we take dispatch function provided by connect and pass it to the component returned from withDeepLinking(App). The full code of App.js file.

// App.js import React from 'react';

import { addNavigationHelpers } from 'react-navigation';

import { connect } from 'react-redux';

import AppNavigator from './AppNavigator';

import withDeepLinking from './withDeepLinking';



function App({ dispatch, nav }) {

return (

<AppNavigator navigation={addNavigationHelpers({

dispatch,

state: nav

})}/>

);

}



const mapStateToProps = (state) => ({

nav: state.nav

});



export default connect(mapStateToProps)(withDeepLinking(App));

Now our application logic is implemented, all we need to do is configure the app.

Configuring Android

Now when we have implemented deep linking we want to test the application. But before it we need to add some code to AndroidManifest.xml.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example"

android:versionCode="1"

android:versionName="1.0">



<uses-permission android:name="android.permission.INTERNET"/>

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>



<uses-sdk

android:minSdkVersion="16"

android:targetSdkVersion="22"/>



<application

android:name=".MainApplication"

android:allowBackup="true"

android:label="@string/app_name"

android:icon="@mipmap/ic_launcher"

android:theme="@style/AppTheme">

<activity

android:name=".MainActivity"

android:launchMode="singleTask"

android:label="@string/app_name"

android:configChanges="keyboard|keyboardHidden|orientation|screenSize"

android:windowSoftInputMode="adjustResize">

<intent-filter>

<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>

</intent-filter>

<intent-filter android:label="filter_react_native">

<action android:name="android.intent.action.VIEW"/>

<category android:name="android.intent.category.DEFAULT"/>

<category android:name="android.intent.category.BROWSABLE"/>

<data android:scheme="example" android:host="colors"/>

</intent-filter>

</activity>

<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>

</application>



</manifest>

Configuring iOS

First of all, you need to register a URL scheme for your app in your Info.plist. After that you need to link RCTLinking to your project by following the steps described here. If you also want to listen to incoming app links during your app's execution, you'll need to add the following lines to your *AppDelegate.m :

// iOS 9.x or newer

#import <React/RCTLinkingManager.h> - (BOOL)application:(UIApplication *)application

openURL:(NSURL *)url

options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options

{

return [RCTLinkingManager application:application openURL:url options:options];

}

Save changes and run the application.

Testing

Now you can send urls to the application using the next command, which you can run in a terminal window.

$ adb shell am start -W -a android.intent.action.VIEW -d “example://colors/green?textColor=red” com.example

Let’s add the list of urls to the home screen of our application. We are going to use Linking.openURL(url) to open them.