Using native modules
In React Native, we manage dependencies to our project in much the same way as other web or Node applications, that is, using npm, the Node Package Manager. Many of the dependencies we use will contain only React Native JavaScript code, but some will have native code as well. The installation process for dependencies containing native code differs slightly from that of pure JS dependencies.
Installing native modules
In order to build out our new profile page and to make our application a bit more aesthetically pleasing, we'll use an icon library called react-native-vector-icons
. The first thing we will need to do is simply install the library with npm
like we would any other dependency:
npm install --save react-native-vector-icons
This library contains native modules, so we are not done yet. Most libraries that contain native modules will provide one or more additional install steps. The end goal of the installation is to link the native code from the dependency to the platform-specific code of the main project. This can be done in one of a few ways.
The first installation option that most native module libraries will provide is a manual installation. This method is cumbersome and involves updating several project configuration files as well as native code files.
The second option that may be offered is to use a platform-specific package manager in addition to npm. On iOS, this probably means CocoaPods, and on Android, Gradle. These tools work well for the job and, compared to the manual option, is much less painful. However, it also adds two additional package managers and configurations to the project.
If these two options don't sound exactly ideal, you're in luck. A tool was developed to reduce some of the friction involved in using native dependencies called rnmp (react native package manager). Because it is much easier, most libraries are moving toward this option. In fact, it is so common and useful that rnpm was eventually merged into the React Native command line client library, which means it should already be installed.
Now we can use the React Native command line client to link the native files from the react-native-vector-icons
library to our project:
react-native link react-native-vector-icons
We've now downloaded the dependency using npm
and then linked the native code files, so we are all set to begin using icons in our project. We could also combine those two steps into a single command:
react-native install react-native-vector-icons
Using the library
The react-native-vector-icons
library gives us a few components that we can use within our project. The Icon
component can be used to simply display icons anywhere in our project. We will also use the Icon.TabBarItemIOS
component that comes with the library that can be used in place of the React Native component TabBarIOS.Item
and gives us access to a more robust collection of icons.
Profile page
The first thing we need to do is make a Profile
component that can be used to display the profile page. We'll put this new component in a file called src/components/Profile.js
:
import React, { Component } from 'react'; import { View, StyleSheet } from 'react-native'; import Title from './Title'; import AppText from './AppText'; import * as globalStyles from '../styles/global'; export default class Profile extends Component { render() { return ( <View style={[globalStyles.COMMON_STYLES.pageContainer, styles.container]}> <Title>Username</Title> <AppText>Your Name</AppText> </View> ); } } const styles = StyleSheet.create({ container: { justifyContent: 'center', alignItems: 'center' } });
So far, this looks very similar to other components we've made. First import React
, react-native
and other application modules we need:
import React, { Component } from 'react'; import { View, StyleSheet } from 'react-native'; import Title from './Title'; import AppText from './AppText'; import * as globalStyles from '../styles/global';
We then create a class for the Profile
component and define its render()
method. Note that this component in its current state could easily be a functional component, but we're making it a class so that we can add more sophisticated behavior later on:
export default class Profile extends Component { render() { return ( <View style={[globalStyles.COMMON_STYLES.pageContainer, styles.container]}> <Title>Username</Title> <AppText>Your Name</AppText> </View> ); } }
Finally, we add some styles in a StyleSheet
to make the page look better:
const styles = StyleSheet.create({ container: { justifyContent: 'center', alignItems: 'center' } });
Now, we're going to use the react-native-vector-icons
library to add an avatar image as a placeholder for the user's profile picture. The first step here is to import the Icon
component. When we import this component, we have to specify which icon font library we want to use. For our project, we'll use the EvilIcons
icon font library:
import React, { Component } from 'react';
import {
View,
StyleSheet
} from 'react-native';
import Icon from 'react-native-vector-icons/EvilIcons';
import Title from './Title';
import AppText from './AppText';
import * as globalStyles from '../styles/global';
Next, we'll add some additional styles to the components StyleSheet
for the avatar icon:
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center'
},
avatarIcon: {
color: globalStyles.HEADER_TEXT_COLOR,
fontSize: 200
}
});
Finally, we'll add an Icon
in the Profile
component's render()
method. We'll use the new styles and choose the icon named user
:
render() {
return (
<View style={[globalStyles.COMMON_STYLES.pageContainer, styles.container]}>
<Icon
name="user"
style={styles.avatarIcon}
/>
<Title>Username</Title>
<AppText>Your Name</AppText>
</View>
);
}
Now that we have a working profile page, we need to make it accessible from within the application. We'll do this by adding it in the HomeScreen.ios.js
and HomeScreen.android.js
files.
Adding the profile to the iOS home screen
On our iOS home screen, we'll need to add an additional tab for the profile page. We'll also swap out the React Native TabBarIOS.Item
components with the Icon.TabBarItemIOS
component from the icon library:
import React, { PropTypes } from 'react'; import { TabBarIOS, StatusBar } from 'react-native'; import Icon from 'react-native-vector-icons/EvilIcons'; import NewsFeedContainer from '../containers/NewsFeedContainer'; import SearchContainer from '../containers/SearchContainer'; import BookmarksContainer from '../containers/BookmarksContainer'; import Profile from './Profile'; import * as globalStyles from '../styles/global'; //Set the status bar for iOS to light StatusBar.setBarStyle('light-content'); const HomeScreen = ({ selectedTab, tab }) => ( <TabBarIOS barTintColor={globalStyles.BAR_COLOR} tintColor={globalStyles.LINK_COLOR} translucent={false} > <Icon.TabBarItemIOS iconName={'star'} title={'News'} selected={selectedTab === 'newsFeed'} onPress={() => tab('newsFeed')} > <NewsFeedContainer/> </Icon.TabBarItemIOS> <Icon.TabBarItemIOS iconName={'search'} title={'Search'} selected={selectedTab === 'search'} onPress={() => tab('search')} > <SearchContainer/> </Icon.TabBarItemIOS> <Icon.TabBarItemIOS iconName={'paperclip'} title={'Bookmarks'} selected={selectedTab === 'bookmarks'} onPress={() => tab('bookmarks')} > <BookmarksContainer /> </Icon.TabBarItemIOS> <Icon.TabBarItemIOS iconName={'user'} title={'Profile'} selected={selectedTab === 'profile'} onPress={() => tab('profile')} > <Profile /> </Icon.TabBarItemIOS> </TabBarIOS> ); HomeScreen.propTypes = { selectedTab: PropTypes.string, tab: PropTypes.func.isRequired }; export default HomeScreen;
First, we add an import statement for our new Profile
component as well as one for the Icon
component:
import React, { PropTypes } from 'react'; import { TabBarIOS, StatusBar } from 'react-native'; import Icon from 'react-native-vector-icons/EvilIcons'; import NewsFeedContainer from '../containers/NewsFeedContainer'; import SearchContainer from '../containers/SearchContainer'; import BookmarksContainer from '../containers/BookmarksContainer'; import Profile from './Profile'; import * as globalStyles from '../styles/global';
Next, we'll change each of the tab bar items to use Icon.TabBarItemIOS
. This component needs two new props, one called iconName
, which tells the component which icon to render
, and one called title
, which specifies the text that should be shown below the tab:
<Icon.TabBarItemIOS iconName={'star'} title={'News'} selected={selectedTab === 'newsFeed'} onPress={() => tab('newsFeed')} > <NewsFeedContainer /> </Icon.TabBarItemIOS>
Notice that we've changed the name of the first tab to use the more appropriate News title. We can do this because now that we aren't using system icons, we have complete control over the text displayed.
Finally, we need to add a new tab for the profile page that follows the same pattern as our other tabs. For this tab, we'll use the same user
icon that we used for the avatar:
<Icon.TabBarItemIOS iconName={'user'} title={'Profile'} selected={selectedTab === 'profile'} onPress={() => tab('profile')} > <Profile /> </Icon.TabBarItemIOS>
In order to make it selectable, we'll also need to add a new profile tab to the available routes in the navigationReducer.js
file:
const routes = {
home: {
key: 'home',
component: HomeScreenContainer,
index: 0,
routes: [
{ key: 'newsFeed', modal: undefined },
{ key: 'search' },
{ key: 'bookmarks' },
{ key: 'profile' }
]
},
intro: {
key: 'intro',
component: IntroScreen
},
onboarding: {
key: 'onboarding',
component: Onboarding
}
};
We should now be able to open the application in iOS and see our new, much more attractive, tab bar icons as well as the profile page as shown in the following screenshot:
Adding the profile to the Android home screen
We will also need to update our Android home screen, which uses a drawer layout instead of a tab bar to account for the new profile page:
import React, { Component, PropTypes } from 'react'; import { DrawerLayoutAndroid, View, StyleSheet } from 'react-native'; import NewsFeedContainer from '../containers/NewsFeedContainer'; import SearchContainer from '../containers/SearchContainer'; import BookmarksContainer from '../containers/BookmarksContainer'; import Profile from './Profile'; import AppText from './AppText'; import * as globalStyles from '../styles/global'; const navConfig = { order: ['newsFeed', 'search', 'bookmarks', 'profile'], newsFeed: { title: 'News', view: <NewsFeedContainer />, tab: 'newsFeed' }, search: { title: 'Search', view: <SearchContainer />, tab: 'search' }, bookmarks: { title: 'Bookmarks', view: <BookmarksContainer />, tab: 'bookmarks' }, profile: { title: 'Profile', view: <Profile />, tab: 'profile' } }; export default class HomeScreen extends Component { constructor(props) { super(props); this.renderDrawer = this.renderDrawer.bind(this); this.showNav = this.showNav.bind(this); } showNav() { this.drawer.openDrawer(); } renderDrawer() { return ( <View style={styles.drawer}> {navConfig.order.map(key => ( <AppText key={key} style={styles.drawerItem} onPress={() => { this.props.tab(navConfig[key].tab); this.drawer.closeDrawer(); }} > {navConfig[key].title} </AppText> ))} </View> ); } render() { return ( <DrawerLayoutAndroid ref={(c) => { this.drawer = c; }} drawerWidth={310} drawerPosition={DrawerLayoutAndroid.positions.Left} drawerBackgroundColor="rgba(0,0,0,0.5)" renderNavigationView={this.renderDrawer} > <View style={styles.container}> <AppText style={styles.menuButton} onPress={this.showNav} >Menu</AppText> {navConfig[this.props.selectedTab].view} </View> </DrawerLayoutAndroid> ); } } HomeScreen.propTypes = { selectedTab: PropTypes.string, tab: PropTypes.func.isRequired }; const styles = StyleSheet.create({ container: { backgroundColor: globalStyles.BG_COLOR, flex: 1 }, drawer: { backgroundColor: globalStyles.BG_COLOR, flex: 1, padding: 10 }, drawerItem: { fontSize: 20, marginBottom: 5 }, menuButton: { marginHorizontal: 10, marginTop: 10, color: globalStyles.LINK_COLOR } });
We first import the Profile
component:
import React, { Component, PropTypes } from 'react';
import {
DrawerLayoutAndroid,
View,
StyleSheet
} from 'react-native';
import NewsFeedContainer from '../containers/NewsFeedContainer';
import SearchContainer from '../containers/SearchContainer';
import BookmarksContainer from '../containers/BookmarksContainer';
import Profile from './Profile';
import AppText from './AppText';
import * as globalStyles from '../styles/global';
We then need to add the profile to the navConfig
object in order to add it to the drawer's menu:
const navConfig = {
order: ['newsFeed', 'search', 'bookmarks', 'profile'],
newsFeed: {
title: 'News',
view: <NewsFeedContainer />,
tab: 'newsFeed'
},
search: {
title: 'Search',
view: <SearchContainer />,
tab: 'search'
},
bookmarks: {
title: 'Bookmarks',
view: <BookmarksContainer />,
tab: 'bookmarks'
},
profile: {
title: 'Profile',
view: <Profile />,
tab: 'profile'
}
};
Note that we have to add a new profile
key to this object in addition to the string 'profile'
to the array describing the order of the menu options. Once we've added these things, our Android application should have the new profile page in its drawer menu:
We've now successfully incorporated an open source native module into both the iOS and Android versions of our application. Next, we'll develop our profile page further and write our own native modules.