Using the Camera in React Native

In the last few articles, we have been working with React Native and have learned how to use some of React Native’s built in component. Most recently, we learned how to navigate between different screens using React Navigation.

One thing we haven’t covered yet, is getting access to the camera and camera roll in a React Native app. Now a days, it seems like every app has access to the phone’s camera. It is used to take photos, to scan QR codes, augmented reality and much more. A lot of these apps can also access the phone’s camera roll to either save photos or allow a user to select a photo from the camera roll. Therefore, in this article, we will be learning how to gain access to the camera and camera roll.

Getting Started

I will be working on a Mac, using Visual Studio Code as my editor and will run the app on the iOS simulator. If you are using Windows or are targeting Android, I will test the app on the Android emulator at the end of the article.

If you are working with Expo, we will be creating a different project after completing the React Native project.

Let’s begin by creating a new React Native project. I will be calling this project, RNCamera. Run the following code in the Terminal.

react-native init RNCamera

Now that we have our project created, let’s go create a src folder to hold our screens and components folders. Here is how our project will be structured.

Once you have the folders created, create a new file called Main.js in the screens folder. Then we need to make changes to the App.js file. Here is the code for App.js and Main.js

App.js

import React, { Component } from 'react';
import Main from './src/screens/Main';
class App extends Component {
render() {
return <Main />
}
}
export default App;

Main.js

import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1
}
})
class Main extends Component {
render() {
return (
<View style={styles.container} />
)
}
}
export default Main;

The plan here will be to have a one-page application consisting of two parts. The first part is going to be an image component. The second component will be a button that when pressed, will allow the user to either take or choose an image from their phone.

Let’s first start with the image component. Create a new file called PhotoComponent.js, inside of the components folder. Then import this new file in Main.js, it will look like this.

Main.js

import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import PhotoComponent from '../components/PhotoComponent'
const styles = StyleSheet.create({
container: {
flex: 1
}
})
class Main extends Component {
render() {
return (
<View style={styles.container}>
<PhotoComponent />
</View>
)
}
}
export default Main;

Now, in PhotoComponent.js, let’s use React Native’s Image component to display an image of a camera. I downloaded two images and stored them inside of a new folder I created, called resources. The first image is one of a hexagon, which I will use as a background, and the second is that of a camera, which will be on top of the hexagon.

Here is the code for the PhotoComponent.js file.

import React, { Component } from 'react';
import { Dimensions, Image, StyleSheet, View } from 'react-native';
const width = Dimensions.get('window').width;
const largeContainerSize = width / 2;
const largeImageSize = width / 4;
const styles = StyleSheet.create({
container: {
flex: 3,
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 10
},
containerSize: {
width: largeContainerSize,
height: largeContainerSize,
alignItems: 'center',
justifyContent: 'center',
tintColor: 'grey'
},
imageSize: {
width: largeImageSize,
alignItems: 'center',
justifyContent: 'center',
position: 'absolute'
},
})
class PhotoComponent extends Component {
render() {
return (
<View style={styles.container}>
<Image
resizeMode='contain'
style={styles.containerSize}
source={require('../resources/background.png')}
/>
<Image
resizeMode='contain'
style={styles.imageSize}
source={require('../resources/camera.png')}
/>
</View>
)
}
}
export default PhotoComponent;

The first two lines are imports we will be using from React and React Native.

The one import that we haven’t used before is Dimensions. This will allow us to get the dimensions of the device the app is running on, both height and width. We will use Dimensions to size our images dynamically based on the user’s screen size.

The next couple lines are constants that will be used to size the images. The first one gets the width of the screen. The next line, const largeContainerSize, is set to half the width of the screen and it will be used for the background image. The next one, largeImageSize, is set to a quarter of the screen’s width.

Then we have our styling. Our container has a flex value of 3 because I want this component to take up most of the screen. In the containerSize, which is the styling for the background image, we give it a tintColor of grey. This changes the color of the original image. And finally, in imageSize, which is the styling for the camera image, we give it a position of absolute because we want it to lay on top of the background image. The other properties that I didn’t mention, are to used to center the images, give it some padding and give it a specific size.

Then we have the class. Here we are returning a View with two Images. The first image is the background image and the second is the camera image.

Now save the files and run the app using the following command.

react-native run-ios

Depending on the images you chose, you may have something like this.

Great! Time to add a button.

Begin by creating a button component called, ButtonComponent.js in the components folder. Then import it in Main.js and add it in the render function, below the PhotoComponent.

Our button will be using an icon, which we will get from a third party library. We will be using react-native-vector-icons and to do so we must first install it, then link it.

To install react-native-vector-icons, run the following command while inside of your project directory.

npm install --save react-native-vector-icons

Once installed, run the following command to link it.

react-native link react-native-vector-icons

With that out the way, let’s work on ButtonComponent.js file. We will import from React and React Native. Import Icon from react-native-vector-icons. Then comes the styling and the class. The class consist of a TouchableOpacity, Icon and View components. The View will be used to create a round gray background for the button. Here is the code.

ButtonComponent.js

import React, { Component } from 'react';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
const styles = StyleSheet.create({
buttonContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
buttonBorder: {
borderColor: 'grey',
borderWidth: 1,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 35,
width: 70,
height: 70,
backgroundColor: 'grey'
},
})
class ButtonComponent extends Component {
render() {
return (
<TouchableOpacity style={styles.buttonContainer}>
<View style={styles.buttonBorder}>
<Icon
name='plus'
size={35}
color='white'/>
</View>
</TouchableOpacity>
)
}
}
export default ButtonComponent;

Save the files and reload the app. If you come upon any errors, close the Metro Bundler and run the project again.

Button looks good. We used the plus icon from FontAwesome and if you want to use different icon, go to https://fontawesome.com/icons?d=gallery to check out their options.

Time to gain access to the camera through React Native. We will be installing react-native-image-picker, which is, “A React Native module that allows you to use native UI to select a photo/video from the device library or directly from the camera.” You can learn more about it at https://github.com/react-native-community/react-native-image-picker.

Begin by installing react-native-image-picker. Use the following command in the Terminal.

npm install --save react-native-image-picker

Once installed, link it by using the following command.

react-native link react-native-image-picker

Now that it is linked, we need to go into the Android and iOS native code to ask the user for permission to take photos or to use an image from their camera roll.

Let’s begin with iOS. Inside of the iOS folder, open the RNCamera folder and open the info.plist file. In this file add the following between the <dict> tags.

<key>NSPhotoLibraryUsageDescription</key>
<string>$(PRODUCT_NAME) would like access to your photo gallery</string>
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) would like to use your camera</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>$(PRODUCT_NAME) would like to save photos to your photo gallery</string>

This code will ask iOS users for permission. Time to do the same for Android users. Head to the Android folder and the AndroidManifest.xml file will be under app/src/main. In it add the following code, which can be added below the code asking for user permission to access the internet at the top of the file.

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

To learn more about the setup, please visit https://github.com/react-native-community/react-native-image-picker/blob/master/docs/Install.md.

With react-native-image-picker installed and the permission code added, we can now add it to our Main.js file.

We will begin by importing react-native-image-picker, using constructor and creating a state, creating a function for the image picker and passing the onPress prop to ButtonComponent. Here is the code.

Main.js

import ImagePicker from "react-native-image-picker";
const styles = StyleSheet.create({
container: {
flex: 1
}
})
class Main extends Component {
constructor(props) {
super(props)
this.state = {
uploadSource: null
}
}
selectPhotoTapped() {
const options = {
quality: 1.0,
maxWidth: 200,
maxHeight: 200,
storageOptions: {
skipBackup: true
}
};
ImagePicker.showImagePicker(options, response => {
console.log("Response = ", response);
if (response.didCancel) {
console.log("User cancelled photo picker");
} else if (response.error) {
console.log("ImagePicker Error: ", response.error);
} else {
let source = { uri: response.uri };
this.setState({
uploadSource: source
});
}
});
}
render() {
return (
<View style={styles.container}>
<PhotoComponent />
<ButtonComponent onPress={this.selectPhotoTapped.bind(this)}/>
</View>
)
}
}

The selectPhotoTapped() function, starts with a constant, option, which sets the max width and max height of the image. Next, we have ImagePicker.showImagePicker, which opens the image picker and returns console logs if the user cancels it or there is an error. If they choose or take a picture, then the state is updated to have upLoadSource equal to the source of the image. Then this function is passed as a prop to ButtonComponent, so that the TouchableOpacity button has access to the function.

Now go to ButtonComponent.js and pass the onPress prop to the TouchableOpacity component. Also, since this component does not use state or lifecycle functions, we can make a stateless function.

ButtonComponent.js

import React, { Component } from 'react';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
const styles = StyleSheet.create({
buttonContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
buttonBorder: {
borderColor: 'grey',
borderWidth: 1,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 35,
width: 70,
height: 70,
backgroundColor: 'grey'
},
})
const ButtonComponent = ({ onPress }) => (
<TouchableOpacity onPress={onPress} style={styles.buttonContainer}>
<View style={styles.buttonBorder}>
<Icon
name='plus'
size={35}
color='white'/>
</View>
</TouchableOpacity>
)
export default ButtonComponent;

Save the files and reload the app. If you run into any issues, try closing the Metro Bundler and run the react-native run-ios command again.

Great! The option to take a photo or choose one from the library appears. But if we pick an image from the library will it work? Let’s try it. Press the Choose from Library button and this will happen.

That’s a good sign. It shows us that the permission code we used worked. Let’s allow it and continue. Here is the next screen.

I’m going to pick the first photo in the Camera Roll folder.

Wait, nothing happened. This is because we are not passing upLoadSource to the PhotoComponent. Before we continue, let’s make sure that upLoadSource has something set to it. To check that upLoadSoucre has a value set to it, we will use console log. Add this line of code in the selectPhotoTapped function, right after setting the state.

Main.js

} else {
let source = { uri: response.uri };
this.setState({
uploadSource: source
);
console.log(this.state.uploadSource)
}

Save the file. Then in the simulator, press both the Command and D buttons to option up the React Native Development options. If you are using the Android emulator on a Mac, press Command and M. If you are using the Android emulator on a Windows computer, press Control and M. Then select Debug JS Remotely, and this will open up a tab in Google Chrome with the URL http://localhost:8081/debugger-ui. If you do not have Google Chrome, please download it or head over to, https://facebook.github.io/react-native/docs/debugging, for other options.

Once the Google Chrome tab opens up, select View from the top menu and then select Developer/Developer Tools. With the debugger now running, reload the app and select an image from the camera roll and see what is displayed in the console.

Awesome! We see that our upLoadSource state has the url of the image. We also see the other console log I added which was meant to show more information about the image is displaying too. The other console logs are meant to show only if there are errors.

Now we should pass upLoadSource to our PhotoComponent. You can stop debugging remotely for now by pressing Command and D, Command and M, or Control and M, then selecting Stop Remote JS Debugging.

Pass the state of uploadSource to the PhotoComponent.

Main.js

<View style={styles.container}>
<PhotoComponent uri={this.state.uploadSource} />
<ButtonComponent onPress={this.selectPhotoTapped.bind(this)}/>
</View>

Then in PhotoComponent, we will check to whether we have a source for an image. To do this we will use the conditional operator “?”.

PhotoComponent.js

renderDefault() {
return (
<View style={styles.container}>
<Image
resizeMode='contain'
style={styles.containerSize}
source={require('../resources/background.png')}
/>
<Image
resizeMode='contain'
style={styles.imageSize}
source={require('../resources/camera.png')}
/>
</View>
)
}
renderImage() {
return (
<View style={styles.container}>
<Image
resizeMode='contain'
style={styles.imageSize}
source={this.props.uri}/>
</View>
)
}
render() {
const displayImage = this.props.uri ? this.renderImage() : this.renderDefault()
return (
<View style={styles.container}>
{displayImage}
</View>
)
}

Inside of the render() function we create a variable named displayImage and it is equal to a conditional operator. If this.props.uri is not null and has a value, then the renderImage() function is called, else the renderDefault() function is called. This variable, displayImage, replaces the code we had between the View tags in the render() function, which was the background image and the camera image. The background image and camera image, are placed in the renderDefault() function. The renderImage() function is where our chosen image will render.

Save the files and reload the app then add a photo from the phone’s camera roll.

Ok, not perfect but the image I chose did display. Let’s make a new set of styles to make this image a bit bigger.

PhotoComponent.js

chosenImage: {
width: width / 1.25,
height: width / 1.25,
alignItems: 'center',
justifyContent: 'center',
position: 'absolute'
}
renderImage() {
return (
<View style={styles.container}>
<Image
resizeMode='contain'
style={styles.chosenImage}
source={this.props.uri}/>
</View>
)
}

The styling is very similar to the camera image, but we are dividing by 1.25 instead of 4, which will make our chosen image much bigger.

Save the files, reload the app and try it again.

That’s much better! The image looks great and we can replace it by pressing on the plus button and choosing another image.

I think it a good time to test this code on Android. Begin by opening the Android emulator, then run the following command.

react-native run-android

It seems like the Android emulator does not have any photos in the camera roll, but you are able to take a photo. This is the result of taking a photo.

Great! It works for Android too. And if you try to select an image from the camera roll, you will see that the image we took is saved there.

Before we get into Expo, here is the code for RNCamera project we created.

Main.js

import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import PhotoComponent from '../components/PhotoComponent';
import ButtonComponent from '../components/ButtonComponent';
import ImagePicker from "react-native-image-picker";
const styles = StyleSheet.create({
container: {
flex: 1
}
})
class Main extends Component {
constructor(props) {
super(props)
this.state = {
uploadSource: null
}
}
selectPhotoTapped() {
const options = {
quality: 1.0,
maxWidth: 200,
maxHeight: 200,
storageOptions: {
skipBackup: true
}
};
ImagePicker.showImagePicker(options, response => {
console.log("Response = ", response);
if (response.didCancel) {
console.log("User cancelled photo picker");
} else if (response.error) {
console.log("ImagePicker Error: ", response.error);
} else {
let source = { uri: response.uri };
this.setState({
uploadSource: source
});
console.log(this.state.uploadSource)
}
});
}
render() {
return (
<View style={styles.container}>
<PhotoComponent uri={this.state.uploadSource} />
<ButtonComponent onPress={this.selectPhotoTapped.bind(this)}/>
</View>
)
}
}
export default Main;

ButtonComponent.js

import React from 'react';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
const styles = StyleSheet.create({
buttonContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
buttonBorder: {
borderColor: 'grey',
borderWidth: 1,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 35,
width: 70,
height: 70,
backgroundColor: 'grey'
},
})
const ButtonComponent = ({ onPress }) => (
<TouchableOpacity onPress={onPress} style={styles.buttonContainer}>
<View style={styles.buttonBorder}>
<Icon
name='plus'
size={35}
color='white'/>
</View>
</TouchableOpacity>
)
export default ButtonComponent;

PhotoComponent.js

import React, { Component } from 'react';
import { Dimensions, Image, StyleSheet, View } from 'react-native';
const width = Dimensions.get('window').width;
const largeContainerSize = width / 2;
const largeImageSize = width / 4;
const styles = StyleSheet.create({
container: {
flex: 3,
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 10
},
containerSize: {
width: largeContainerSize,
height: largeContainerSize,
alignItems: 'center',
justifyContent: 'center',
tintColor: 'grey'
},
imageSize: {
width: largeImageSize,
height: largeImageSize,
alignItems: 'center',
justifyContent: 'center',
position: 'absolute'
},
chosenImage: {
width: width / 1.25,
height: width / 1.25,
alignItems: 'center',
justifyContent: 'center',
position: 'absolute'
}
})
class PhotoComponent extends Component {
renderDefault() {
return (
<View style={styles.container}>
<Image
resizeMode='contain'
style={styles.containerSize}
source={require('../resources/background.png')}
/>
<Image
resizeMode='contain'
style={styles.imageSize}
source={require('../resources/camera.png')}
/>
</View>
)
}
renderImage() {
return (
<View style={styles.container}>
<Image
resizeMode='contain'
style={styles.chosenImage}
source={this.props.uri}/>
</View>
)
}
render() {
const displayImage = this.props.uri ? this.renderImage() : this.renderDefault()
return (
<View style={styles.container}>
{displayImage}
</View>
)
}
}
export default PhotoComponent;

App.js

import React, { Component } from 'react';
import Main from './src/screens/Main'
class App extends Component {
render() {
return <Main />
}
}
export default App;

Using the Camera in Expo

Instead of using creating an Expo project and using the code we already wrote, we will start from scratch. This is because Expo has an API for picking an image or taking one with the phone that we will be using. To read more about it, here is the link, https://docs.expo.io/versions/latest/sdk/imagepicker/.

We will create a new project using Expo and take most of the code we have written. The only thing that will change is the code for selecting the image.

Begin by closing everything that relates to the RNCamera project. We then use the Terminal to create a new Expo project, called ExpoCamera, using the following command.

expo init ExpoCamera

When prompted to choose a template, pick blank template. Then enter the name of the project and use Yarn if you have it.

Once the project is created, copy over the App.js and src folder from RNCamera to ExpoCamera project. Before running, we will need to remove a few things. Here are how the files will look like in your ExpoCamera project.

App.js

import React, { Component } from 'react';
import Main from './src/screens/Main'
class App extends Component {
render() {
return <Main />
}
}
export default App;

Main.js

import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import PhotoComponent from '../components/PhotoComponent';
import ButtonComponent from '../components/ButtonComponent';
const styles = StyleSheet.create({
container: {
flex: 1
}
})
class Main extends Component {
render() {
return (
<View style={styles.container}>
<PhotoComponent />
<ButtonComponent />
</View>
)
}
}
export default Main;

PhotoComponent.js

import React, { Component } from 'react';
import { Dimensions, Image, StyleSheet, View } from 'react-native';
const width = Dimensions.get('window').width;
const largeContainerSize = width / 2;
const largeImageSize = width / 4;
const styles = StyleSheet.create({
container: {
flex: 3,
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 10
},
containerSize: {
width: largeContainerSize,
height: largeContainerSize,
alignItems: 'center',
justifyContent: 'center',
tintColor: 'grey'
},
imageSize: {
width: largeImageSize,
height: largeImageSize,
alignItems: 'center',
justifyContent: 'center',
position: 'absolute'
},
chosenImage: {
width: width / 1.25,
height: width / 1.25,
alignItems: 'center',
justifyContent: 'center',
position: 'absolute'
}
})
class PhotoComponent extends Component {
renderDefault() {
return (
<View style={styles.container}>
<Image
resizeMode='contain'
style={styles.containerSize}
source={require('../resources/background.png')}
/>
<Image
resizeMode='contain'
style={styles.imageSize}
source={require('../resources/camera.png')}
/>
</View>
)
}
renderImage() {
return (
<View style={styles.container}>
<Image
resizeMode='contain'
style={styles.chosenImage}
source={this.props.uri}/>
</View>
)
}
render() {
const displayImage = this.props.uri ? this.renderImage() : this.renderDefault()
return (
<View style={styles.container}>
{displayImage}
</View>
)
}
}
export default PhotoComponent;

ButtonComponent.js

import React from 'react';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
const styles = StyleSheet.create({
buttonContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
buttonBorder: {
borderColor: 'grey',
borderWidth: 1,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 35,
width: 70,
height: 70,
backgroundColor: 'grey'
},
})
const ButtonComponent = ({ onPress }) => (
<TouchableOpacity onPress={onPress} style={styles.buttonContainer}>
<View style={styles.buttonBorder}>
<Icon
name='plus'
size={35}
color='white'/>
</View>
</TouchableOpacity>
)
export default ButtonComponent;

Most of what was removed was related to react-native-image-picker. Now with that out the way, save the files and run the app.

App looks great. Time to implement Expo’s ImagePicker API.

First thing we must do is install some Expo components. You will need to install Permissions, Constants, and ImagePicker by using the following command.

expo install expo-image-picker expo-permissions expo-constants

Then in Main.js, we will add the constructor with our state, upLoadSource. Then we will use a componentDidMount() function which will call another function called getPermissionAsync. This will be done to ask the user for their permission to gain access to the camera roll.

Then we will create a function called _pickImage, which will launch the camera roll and set upLoadSource to the source of the image we pick.

Last thing to do is to go to PhotoComponent and make a change to the Image component responsible for the photo we pick.

Main.js

import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import Constants from 'expo-constants';
import * as Permissions from 'expo-permissions';
import PhotoComponent from '../components/PhotoComponent';
import ButtonComponent from '../components/ButtonComponent';
const styles = StyleSheet.create({
container: {
flex: 1
}
})
class Main extends Component {
constructor(props) {
super(props)
this.state = {
uploadSource: null
}
}
componentDidMount() {
this.getPermissionAsync();
}
getPermissionAsync = async () => {
if (Constants.platform.ios) {
const { status } = await Permissions.askAsync(Permissions.CAMERA_ROLL);
if (status !== 'granted') {
alert('Sorry, we need camera roll permissions to make this work!');
}
}
}
_pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
});
console.log(result);
if (!result.cancelled) {
this.setState({ uploadSource: result.uri });
}
};
render() {
return (
<View style={styles.container}>
<PhotoComponent uri={this.state.uploadSource} />
<ButtonComponent onPress={this._pickImage}/>
</View>
)
}
}
export default Main;

PhotoComponent.js

renderImage() {
return (
<View style={styles.container}>
<Image
resizeMode='contain'
style={styles.chosenImage}
source={{uri: this.props.uri}}/>
</View>
)
}

Now save the files and reload the app.

As you may have noticed, we can only select an image from the camera roll. This is because in _pickImage function, we are using launchImageLibraryAsync. This launches the camera roll and if we wanted to have an option to take a photo, we would need to add another permission request and another button to handle this.

Let’s create another button that will let us take a picture. In Main.js, copy ButtonComponent and paste it right below. We will be making changes to the onPress and will also pass it a prop for icon.

We got two buttons but that doesn’t look good. Wrap these buttons in a View component with flexDirection of row and paddingBottom of 40.

Main.js

render() {
return (
<View style={styles.container}>
<PhotoComponent uri={this.state.uploadSource} />
<View style={{ flexDirection: 'row', paddingBottom: 40 }}>
<ButtonComponent onPress={this._pickImage}/>
<ButtonComponent onPress={this._pickImage}/>
</View>
</View>
)
}

Much better. Time to make changes to the icons of these buttons. We will make the left button the camera button and will use a camera icon. For the right button, we will make it the gallery button and use an image icon.

Main.js

render() {
return (
<View style={styles.container}>
<PhotoComponent uri={this.state.uploadSource} />
<View style={{ flexDirection: 'row', paddingBottom: 40 }}>
<ButtonComponent onPress={this._pickImage} icon='camera'/>
<ButtonComponent onPress={this._pickImage} icon='image'/>
</View>
</View>
)
}

ButtonComponent.js

const ButtonComponent = ({ onPress, icon }) => (
<TouchableOpacity onPress={onPress} style={styles.buttonContainer}>
<View style={styles.buttonBorder}>
<Icon
name={icon}
size={35}
color='white'/>
</View>
</TouchableOpacity>
)

Great! The buttons look much better and the user can distinguish between the two. Time to work on onPress. For the second button we can leave it, but we need to create a new function for the other one. We also need to include another permission request.

Main.js

getPermissionAsync = async () => {
if (Constants.platform.ios) {
const { status } = await Permissions.askAsync(Permissions.CAMERA_ROLL, Permissions.CAMERA);
if (status !== 'granted') {
alert('Sorry, we need camera roll permissions to make this work!');
}
}
}

We add the request for camera right after the request for camera roll.

We will use _pickImage as a guide to create the _takePhoto function. We will replace launchImageLibraryAsync with launchCameraAsync.

Main.js

_takePhoto = async () => {
let result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
});
console.log(result);
if (!result.cancelled) {
this.setState({ uploadSource: result.uri });
}
};

Last thing to do before running the app is to change the onPress of the first button. Then save the files and give it a try.

Perfect! It is working. We can use the left button to take photos, which can’t be done in the iOS simulator, or the right button to pick a photo from the camera roll.

Here is the code for the Expo project we just worked on.

Main.js

import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import Constants from 'expo-constants';
import * as Permissions from 'expo-permissions';
import PhotoComponent from '../components/PhotoComponent';
import ButtonComponent from '../components/ButtonComponent';
const styles = StyleSheet.create({
container: {
flex: 1
}
})
class Main extends Component {
constructor(props) {
super(props)
this.state = {
uploadSource: null
}
}
componentDidMount() {
this.getPermissionAsync();
}
getPermissionAsync = async () => {
if (Constants.platform.ios) {
const { status } = await Permissions.askAsync(Permissions.CAMERA_ROLL, Permissions.CAMERA);
if (status !== 'granted') {
alert('Sorry, we need camera roll permissions to make this work!');
}
}
}
_pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
});
console.log(result);
if (!result.cancelled) {
this.setState({ uploadSource: result.uri });
}
};
_takePhoto = async () => {
let result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
});
console.log(result);
if (!result.cancelled) {
this.setState({ uploadSource: result.uri });
}
};
render() {
return (
<View style={styles.container}>
<PhotoComponent uri={this.state.uploadSource} />
<View style={{ flexDirection: 'row', paddingBottom: 40 }}>
<ButtonComponent onPress={this._takePhoto} icon='camera'/>
<ButtonComponent onPress={this._pickImage} icon='image'/>
</View>
</View>
)
}
}
export default Main;

ButtonComponent.js

import React from 'react';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
const styles = StyleSheet.create({
buttonContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
buttonBorder: {
borderColor: 'grey',
borderWidth: 1,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 35,
width: 70,
height: 70,
backgroundColor: 'grey'
},
})
const ButtonComponent = ({ onPress, icon }) => (
<TouchableOpacity onPress={onPress} style={styles.buttonContainer}>
<View style={styles.buttonBorder}>
<Icon
name={icon}
size={35}
color='white'/>
</View>
</TouchableOpacity>
)
export default ButtonComponent;

PhotoComponent.js

import React, { Component } from 'react';
import { Dimensions, Image, StyleSheet, View } from 'react-native';
const width = Dimensions.get('window').width;
const largeContainerSize = width / 2;
const largeImageSize = width / 4;
const styles = StyleSheet.create({
container: {
flex: 3,
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 10
},
containerSize: {
width: largeContainerSize,
height: largeContainerSize,
alignItems: 'center',
justifyContent: 'center',
tintColor: 'grey'
},
imageSize: {
width: largeImageSize,
height: largeImageSize,
alignItems: 'center',
justifyContent: 'center',
position: 'absolute'
},
chosenImage: {
width: width / 1.25,
height: width / 1.25,
alignItems: 'center',
justifyContent: 'center',
position: 'absolute'
}
})
class PhotoComponent extends Component {
renderDefault() {
return (
<View style={styles.container}>
<Image
resizeMode='contain'
style={styles.containerSize}
source={require('../resources/background.png')}
/>
<Image
resizeMode='contain'
style={styles.imageSize}
source={require('../resources/camera.png')}
/>
</View>
)
}
renderImage() {
return (
<View style={styles.container}>
<Image
resizeMode='contain'
style={styles.chosenImage}
source={{uri: this.props.uri}}/>
</View>
)
}
render() {
const displayImage = this.props.uri ? this.renderImage() : this.renderDefault()
return (
<View style={styles.container}>
{displayImage}
</View>
)
}
}
export default PhotoComponent;

App.js

import React, { Component } from 'react';
import Main from './src/screens/Main'
class App extends Component {
render() {
return <Main />
}
}
export default App;

Awesome work! We create two projects, a RNCamera and ExpoCamera. These two projects use the phone’s camera to take pictures or the phone’s camera roll to display a photo on the screen. We learned how to get the user’s permission to gain access to the camera and camera roll, how to use icons with react-native-vector-icons, how to layer two images on top of each other, and how to display the photo we took or chose.

So where can you go from here? Play with the code. Change the size of the images. Or try using a video or taking a video. With what we have learned in this article you are on your way to creating an app with an awesome camera feature.

Exploring React Native – Part 1.1

In the last article, titled “Exploring React Native”, we used a few components to create a simple app. The app consisted of an Image component, a couple Text components, data that changed with user interaction, and a couple of buttons created with the Button component and TouchableOpacity component.  We styled each component and at the end, had a counter app.

But there are a lot of components we did not cover and the ones we did cover, can be used in other ways. So, in this article, we will continue to use the project from the previous article to learn more about React Native’s components. The components we will be focusing on in this article are the ScrollView and View components. The ScrollView component is similar to View but allows for scrolling. The View component is one that we used in the previously but in this article, we will be using it to create sections in the app. As well, we will be passing a network image to the Image component and will learn a bit more about styling.

Let’s get started!

More Built In Components

I will be working on a Mac using Visual Studio Code as my editor, run the app on the iOS simulator and will be working with the “FirstRNProject” project. If you are using Windows or are targeting Android, I will test the app on the Android emulator at the end of the article. This code will also work if you are using Expo and will also be tested later on.

Open the App.js file and this is what we have from last time:

import React, { Component } from 'react';
import { Button, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#bff0d4',
alignItems: 'center',
},
image: {
height: 200,
width: 200,
marginTop: 100,
marginBottom: 20
},
question: {
fontSize: 30,
fontWeight: 'bold',
textAlign: 'center',
color: '#535B60',
padding: 20
},
number: {
fontSize: 60,
fontWeight: 'bold',
textAlign: 'center',
color: '#535B60',
padding: 20
},
plusButton: {
backgroundColor: '#9FC4AD',
width: 200,
borderRadius: 10,
margin: 10
},
minusButton: {
backgroundColor: '#BAAAC4',
width: 200,
borderRadius: 10,
margin: 10
},
buttonText: {
fontSize: 40,
fontWeight: 'bold',
textAlign: 'center',
color: '#535B60'
}
});
class App extends Component {
state = {
raccoons: 0
};
addMore = () => {
this.setState({
raccoons: this.state.raccoons + 1
})
}
removeOne = () => {
if(this.state.raccoons !== 0){
this.setState({
raccoons: this.state.raccoons - 1
})
}
}
render() {
return (
<View style={styles.container}>
<Image style={styles.image} resizeMode='contain' source={require('./img/raccoon.png')} />
<Text style={styles.question} >How many raccoons did you see last night?</Text>
<Text style={styles.number}>{this.state.raccoons}</Text>
<TouchableOpacity onPress={this.addMore} style={styles.plusButton} >
<Text style={styles.buttonText}>PLUS</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.removeOne} style={styles.minusButton}>
<Text style={styles.buttonText}>MINUS</Text>
</TouchableOpacity>
</View>
)
}
}
export default App;

First a quick recap of the code above.

We first imported the components we were going to use from React Native. These components were the Button, Image, StyleSheet, Text, TouchableOpacity and View components. Then we created a styles variables that contained all the styling objects we used to style the components. After that, we created a state for the counter which would change with the pressing of either “PLUS” or “MINUS” buttons. Then inside the render function we had a View component that wrapped the Image, Text and TouchableOpacity components and each was styled accordingly.

Open the Terminal or Command Prompt to run the project. If you are using Visual Studio Code, there is an “integrated terminal, initially starting at the root of your workspace.” Using this terminal, you can run the React Native iOS/Android start commands, or if using Expo, the Expo start command from the editor. You can learn more about Visual Studio Code’s terminal here, https://code.visualstudio.com/docs/editor/integrated-terminal. What you will have is the following:

The app looks great but what if we wanted to create a list of animals, each with its own image, text, counter and buttons? Well, let’s copy the components between the View tags and paste them right after the “MINUS” button but before the closing View tag. Your code will look like this:

<View style={styles.container}>
{/* Raccoon One */}
<Image style={styles.image} resizeMode='contain' source={require('./img/raccoon.png')} />
<Text style={styles.question} >How many raccoons did you see last night?</Text>
<Text style={styles.number}>{this.state.raccoons}</Text>
<TouchableOpacity onPress={this.addMore} style={styles.plusButton} >
<Text style={styles.buttonText}>PLUS</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.removeOne} style={styles.minusButton}>
<Text style={styles.buttonText}>MINUS</Text>
</TouchableOpacity>
{/* Raccoon Two */}
<Image style={styles.image} resizeMode='contain' source={require('./img/raccoon.png')} />
<Text style={styles.question} >How many raccoons did you see last night?</Text>
<Text style={styles.number}>{this.state.raccoons}</Text>
<TouchableOpacity onPress={this.addMore} style={styles.plusButton} >
<Text style={styles.buttonText}>PLUS</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.removeOne} style={styles.minusButton}>
<Text style={styles.buttonText}>MINUS</Text>
</TouchableOpacity>
</View>

When you save the file and reload the app, you will see this:

That doesn’t look right. We can barely see that there is another image of a raccoon at the bottom of the screen and we can’t scroll down to view the rest of the app. In order for the app to scroll we will use the ScrollView component. According to the React Native documentation, “The ScrollView is a generic scrolling container that can host multiple components and views.” There is another option we could use, called FlatList. The difference between the two, is that, ScrollView renders all of its children components, or the components between its tags, at once. While FlatList renders its items when they appear on the screen and removes them once off the screen. Therefore, if you have a large list, using ScrollView will slow down rendering and increase memory usage. For this app, our list will be short and ScrollView will be used but in later articles we will be using the FlatList component.

Let’s first import the ScrollView component and replace the View tags with ScrollView, save the file and reload. This is what will happen:

This error occurs because of the layout prop “alignItems: ‘center’” that is passed to the styling of the ScrollView components. To fix it remove “alignItems: ‘center’” from “container” in styles. Save the file, reload and now the app will look like this:

It’s not perfect but we can now scroll to through the app and see the second image, along with the text and buttons. To fix the styling of the images and buttons simply add “alignSelf: ‘center’” to the “image”, “plusButton” and “minusButton” styles.

image: {
height: 200,
width: 200,
marginTop: 100,
marginBottom: 20,
alignSelf: 'center'
},
plusButton: {
backgroundColor: '#9FC4AD',
width: 200,
borderRadius: 10,
margin: 10,
alignSelf: 'center'
},
minusButton: {
backgroundColor: '#BAAAC4',
width: 200,
borderRadius: 10,
margin: 10,
alignSelf: 'center'
},

Great! Everything is now centered and we can scroll.

It doesn’t make sense to keep track of the number of raccoons twice, so let’s find an image of another animal online.

If you recall, in the previous article we saved the image of the raccoon in our project under the ‘img’ folder, then passed it to the Image component. By doing so we were using a static image. The Image component can display various types of images and what I want to do now is use a network image. I went online and found an image of a pigeon and got the URL to the image.

Now if you replace the location of the second raccoon image with an URL, like this:

<Image style={styles.image} resizeMode='contain' source={require('https://cdn.pixabay.com/photo/2012/04/02/12/43/pigeon-24391_1280.png')} />

Then you will get an error like this:

That’s because, passing a network image is a little different than passing a static image. Inside of “source={}”, replace it with “{uri: ‘URL_OF_THE_IMAGE’}”. It will look like this:

<Image style={styles.image} resizeMode='contain' source={{ uri: ‘URL_OF_THE_IMAGE’ }} />

So if we replace ‘URL_OF_THE_IMAGE’ with the actual URL, we will have this:

<Image style={styles.image} resizeMode='contain' source={{ uri: 'https://cdn.pixabay.com/photo/2012/04/02/12/43/pigeon-24391_1280.png' }} />

Another key difference between static and network images is that, network images require that the dimensions of the image be specified. Our ‘image’ style object has specified width and height dimension, so our image will appear. Had those dimension not been there, the image would not be displayed.

Save the file and reload the app to get this:

Now I know not everyone likes this bird but I liked how it looked and it a bird, I would say, most people see on a regular basis. You can choose another bird or another animal entirely, it’s totally up to you. There’s just a couple more things that need changing, such as the text, adding the new counter data to the state and creating new functions for the new pigeon buttons.

Changing the text is simple, go to the Text component that corresponds to the second animal, in my case the pigeon, and change it to something like, “How many pigeons did you see today?”. Then add “pigeons: 0” to the state and replace “{this.state.raccoons}” with “{this.state.pigeons}” in the following Text component. Next we can rename the existing functions for the buttons to “addRaccoons” and “removeRaccoons” then copy and paste them right below. For the second set of functions, replace “raccoons” with “pigeons”. Remember to keep the camel case coding style and capitalize the “P” in pigeon. Also don’t forget to go to the TouchableOpacity components and rename the functions accordingly. If you are having any issues, here is the code:

class App extends Component {
state = {
raccoons: 0,
pigeons: 0
};
//Raccoon Functions
addRaccoons = () => {
this.setState({
raccoons: this.state.raccoons + 1
})
}
removeRaccoons = () => {
if(this.state.raccoons !== 0){
this.setState({
raccoons: this.state.raccoons - 1
})
}
}
//Pigeon Functions
addPigeons = () => {
this.setState({
pigeons: this.state.pigeons + 1
})
}
removePigeons = () => {
if(this.state.pigeons !== 0){
this.setState({
pigeons: this.state.pigeons - 1
})
}
}
render() {
return (
<ScrollView style={styles.container}>
{/* Raccoon */}
<Image style={styles.image} resizeMode='contain' source={require('./img/raccoon.png')} />
<Text style={styles.question} >How many raccoons did you see last night?</Text>
<Text style={styles.number}>{this.state.raccoons}</Text>
<TouchableOpacity onPress={this.addRaccoons} style={styles.plusButton} >
<Text style={styles.buttonText}>PLUS</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.removeRaccoons} style={styles.minusButton}>
<Text style={styles.buttonText}>MINUS</Text>
</TouchableOpacity>
{/* Pigeon */}
<Image style={styles.image} resizeMode='contain' source={{ uri: 'https://cdn.pixabay.com/photo/2012/04/02/12/43/pigeon-24391_1280.png' }} />
<Text style={styles.question} >How many pigeons did you see today?</Text>
<Text style={styles.number}>{this.state.pigeons}</Text>
<TouchableOpacity onPress={this.addPigeons} style={styles.plusButton} >
<Text style={styles.buttonText}>PLUS</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.removePigeons} style={styles.minusButton}>
<Text style={styles.buttonText}>MINUS</Text>
</TouchableOpacity>
</ScrollView>
)
}
}

I’ve added comments, “{/* Raccoon */}” and “{/* Pigeon */}”, to help indicate that the following set of code corresponds to a particular animal. Writing comments can help you identify sections of code, especially once the code starts to get long and complicated. One thing to note is that when commenting inside JSX, where tags are used like in the above, you need to wrap the comment inside of “{/* YOUR_COMMENT */}”. Outside of JSX, you can use “// YOUR_COMMENT” for a single line comment or “/* YOUR_COMMENT */” for a multi-line comment.

Once saved and reloaded, you will be able to scroll through the app and press the buttons to increase or decrease the counters. Here is how it will look:

Great! Let’s now work on styling the app a bit more by using React Native’s View component.

Right now the app is one continuous page with images, text and buttons. To help separate each section and make the app more user friendly, we will create a border around the raccoon and pigeon set of components. It’s like creating a card for each animal and the card contains all the content for that one subject. This can simply be done by wrapping each set of components in a View and passing it a set of styles.

First, import the View component if you deleted it and create two set of opening and closing View tags. Then copy the set of raccoon components and paste them inside the first View. Repeat for the pigeon components but paste those in the second View. This is what you should have:

<ScrollView style={styles.container}>
{/* Raccoon */}
{/* First View */}
<View>
<Image style={styles.image} resizeMode='contain' source={require('./img/raccoon.png')} />
<Text style={styles.question} >How many raccoons did you see last night?</Text>
<Text style={styles.number}>{this.state.raccoons}</Text>
<TouchableOpacity onPress={this.addRaccoons} style={styles.plusButton} >
<Text style={styles.buttonText}>PLUS</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.removeRaccoons} style={styles.minusButton}>
<Text style={styles.buttonText}>MINUS</Text>
</TouchableOpacity>
</View>
{/* Pigeon */}
{/* Second View */}
<View>
<Image style={styles.image} resizeMode='contain' source={{ uri: 'https://cdn.pixabay.com/photo/2012/04/02/12/43/pigeon-24391_1280.png' }} />
<Text style={styles.question} >How many pigeons did you see today?</Text>
<Text style={styles.number}>{this.state.pigeons}</Text>
<TouchableOpacity onPress={this.addPigeons} style={styles.plusButton} >
<Text style={styles.buttonText}>PLUS</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.removePigeons} style={styles.minusButton}>
<Text style={styles.buttonText}>MINUS</Text>
</TouchableOpacity>
</View>
</ScrollView>

We will now create a new set of styles called “cardStyle” and pass it to both View components. To “cardStyle”, we will add a border color and width. This will create the border around each set of components.

cardStyle: {
borderColor: '#535B60',
borderWidth: 2
}

Save the file and reload the app. Wait a minute, this doesn’t look right.

I can see that there is a line separating the raccoon and pigeon cards but that’s about it. We will need to style this some more.

First add “margin: 20” to “cardStyle”, this will create space between the outside of the border and the edge of the screen. We can then go into the “image” style and remove both margins.

Looking better but I don’t like the border, it’s too boxy. This is a quick fix, add “borderRadius: 10” to “cardStyle”. Also notice that the top border of the raccoon card is being cut off by the iPhone X notch. Let’s add “paddingTop: 20” to the “container” style.

Looking awesome! Our styling is as follows:

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#bff0d4',
paddingTop: 20
},
image: {
height: 200,
width: 200,
alignSelf: 'center'
},
question: {
fontSize: 30,
fontWeight: 'bold',
textAlign: 'center',
color: '#535B60',
padding: 20
},
number: {
fontSize: 60,
fontWeight: 'bold',
textAlign: 'center',
color: '#535B60',
padding: 20
},
plusButton: {
backgroundColor: '#9FC4AD',
width: 200,
borderRadius: 10,
margin: 10,
alignSelf: 'center'
},
minusButton: {
backgroundColor: '#BAAAC4',
width: 200,
borderRadius: 10,
margin: 10,
alignSelf: 'center'
},
buttonText: {
fontSize: 40,
fontWeight: 'bold',
textAlign: 'center',
color: '#535B60'
},
cardStyle: {
borderColor: '#535B60',
borderWidth: 2,
margin: 20,
borderRadius: 10,
}
});

Before continuing with styling, I would like to over the different ways you can style a component.

Although not mentioned before, you can simply pass styles to a component using inline styling. Here is an example:

<View style={{ flex: 1, borderColor: ‘red’, borderWidth: 2 }} >

Or, as we have been doing, we can put all the styling in one location and reference it when needed like this:

const styles = StyleSheet.create({
container: {
flex: 1,
borderColor: ‘red’,
borderWidth: 2
}
});
<View style={styles.cardStyle}>

Both styling methods will style the component the same, but by having the styling outside of the render function, we are making the code cleaner and easier to read.

We can also mix and use both by passing style an array, which can help has pass specific styling to only one particular component, while also passing a set of styles other components use. Here is an example:

<View style={[ styles.container, { margin: 20 } ]} >

The styling is almost perfect but I want to add a bottom margin to the pigeon’s View component because I want some spacing between the bottom of the screen and the bottom of the pigeon card. We will pass “cardStyle” and “marginBottom: 60” to only the second View component. Here is how it is done:

{/* Pigeon */}
{/* Second View */}
<View style={[styles.cardStyle, {marginBottom: 60}]}>
<Image style={styles.image} resizeMode='contain' source={{ uri: 'https://cdn.pixabay.com/photo/2012/04/02/12/43/pigeon-24391_1280.png' }} />
<Text style={styles.question} >How many pigeons did you see today?</Text>
<Text style={styles.number}>{this.state.pigeons}</Text>
<TouchableOpacity onPress={this.addPigeons} style={styles.plusButton} >
<Text style={styles.buttonText}>PLUS</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.removePigeons} style={styles.minusButton}>
<Text style={styles.buttonText}>MINUS</Text>
</TouchableOpacity>
</View>

The bottom of the pigeon card is visible. This is looking great!

Continuing to focus on styling, let’s have the two buttons placed next to each other. Here we will be using the View component again. First inside the raccoon’s View component create a View right after the counter text. Then copy and paste both TouchableOpacity components inside of the View tags. Then inside the pigeon’s View component, do the same for those buttons.

<ScrollView style={styles.container}>
{/* Raccoon */}
<View style={styles.cardStyle}>
<Image style={styles.image} resizeMode='contain' source={require('./img/raccoon.png')} />
<Text style={styles.question} >How many raccoons did you see last night?</Text>
<Text style={styles.number}>{this.state.raccoons}</Text>
{/* Raccoon Buttons */}
<View>
<TouchableOpacity onPress={this.addRaccoons} style={styles.plusButton} >
<Text style={styles.buttonText}>PLUS</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.removeRaccoons} style={styles.minusButton}>
<Text style={styles.buttonText}>MINUS</Text>
</TouchableOpacity>
</View>
</View>
{/* Pigeon */}
<View style={[styles.cardStyle, {marginBottom: 60}]}>
<Image style={styles.image} resizeMode='contain' source={{ uri: 'https://cdn.pixabay.com/photo/2012/04/02/12/43/pigeon-24391_1280.png' }} />
<Text style={styles.question} >How many pigeons did you see today?</Text>
<Text style={styles.number}>{this.state.pigeons}</Text>
{/* Pigeon Buttons */}
<View>
<TouchableOpacity onPress={this.addPigeons} style={styles.plusButton} >
<Text style={styles.buttonText}>PLUS</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.removePigeons} style={styles.minusButton}>
<Text style={styles.buttonText}>MINUS</Text>
</TouchableOpacity>
</View>
</View>
</ScrollView>

If you save the file and reload the app, you will notice that nothing happens. We will need to create a new style called “buttonRow”, pass it “flexDirection: ‘row’”, then pass this style to the buttons’ View components. By default “flexDirection” is set to column, because of this, components are stacked on top of each other. But by setting “flexDirection” to row, the components in that View will be stacked side by side.

buttonRow: {
flexDirection: 'row'
}
{/* Raccoon Button */}
<View style={styles.buttonRow}>
<TouchableOpacity onPress={this.addRaccoons} style={styles.plusButton} >
<Text style={styles.buttonText}>PLUS</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.removeRaccoons} style={styles.minusButton}>
<Text style={styles.buttonText}>MINUS</Text>
</TouchableOpacity>
</View>
{/* Pigeon Buttons */}
<View style={styles.buttonRow}>
<TouchableOpacity onPress={this.addPigeons} style={styles.plusButton} >
<Text style={styles.buttonText}>PLUS</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.removePigeons} style={styles.minusButton}>
<Text style={styles.buttonText}>MINUS</Text>
</TouchableOpacity>
</View>

Now the app looks like this:

Oh no! The buttons don’t fit properly. Let’s make the buttons smaller by changing the text’s size and decreasing its width.

plusButton: {
backgroundColor: '#9FC4AD',
width: 150,
borderRadius: 10,
margin: 10,
alignSelf: 'center'
},
minusButton: {
backgroundColor: '#BAAAC4',
width: 150,
borderRadius: 10,
margin: 10,
alignSelf: 'center'
},
buttonText: {
fontSize: 30,
fontWeight: 'bold',
textAlign: 'center',
color: '#535B60'
},

Better but still needs work. We can decrease the button’s margins and center the button’s View component.

plusButton: {
backgroundColor: '#9FC4AD',
width: 150,
borderRadius: 10,
margin: 5,
alignSelf: 'center'
},
minusButton: {
backgroundColor: '#BAAAC4',
width: 150,
borderRadius: 10,
margin: 5,
alignSelf: 'center'
},
buttonText: {
fontSize: 30,
fontWeight: 'bold',
textAlign: 'center',
color: '#535B60'
},
buttonRow: {
flexDirection: 'row',
alignSelf: 'center'
}

At this point, the app looks wonderful but I have tested our code only on the iOS simulator. I want to make sure for those using Windows or targeting Android, that we have the same results. After opening the Android emulator and running the project, I have this:

The app works! I knew it would but there are times that certain components appear differently on iOS than they do on Android. We saw these differences when working with React Native’s Button component. But in this case, the app looks and works the same on both Android and iOS.

Now for those using Expo, I mentioned at the beginning that the code used in this project would also work for you guys. To ensure this is true, I am going to copy the code and paste it in an Expo project we created a while back called, “FirstExpoProject”. Here is the app on a real iPhone X Max:

Yes, app works and looks great! It could probably use more padding at the top and bottom of the screen. Unfortunately, I do not own an Android device but since the app worked on the Android emulator, I am certain it will work on an Android device.

This is where we will leave off for this article. We added ScrollView to our app, allowing us to the ability to scroll and add more content. Then we added a new animal and passed a network image to the Image component. Lastly, using View and some new styling skills we created cards to contain each subject. I suggest you play around with the code because following steps is one thing but when you try it on your own, that’s when you really learn.

In the next article, we will continue to expand our React Native skills using this project. The code in this project was getting long and could be cleaned up. For one, we will learn about creating components and a few more things. See you in the next article.

Exploring React Native

In the previous article, titled “Getting Started with React Native”, we briefly went over what React Native is. We went through the installation process for the Expo CLI and the React Native CLI. The last thing we did was create an Expo and React Native project and made changes to the code, then run the projects.

But at the end of the article you may have wondered, “Now what?”. In this article I will show you some of the built in components that come with React Native. We will use these components to create a fairly simply one screen app that will keep track of a number that can be increased and decreased by a button. The app will teach you how to add an image, text, buttons, keep track of data and style all these components.

Let’s begin!

Built In Components

Begin by opening your project in the code editor of your choice, remember I use Visual Studio Code. If you recall, in the last article there were two ways to create a project. We created an Expo project, called “FirstExpoProject,” and a React Native project, called “FirstRNProject”. I will be using the “FirstRNProject” project but the code in this article will work for both.

In the Explorer, select App.js, open it and delete all the code inside of the file. Throughout this article I will be showing you the code I write, some times it will only be a snippet of the file and I will use “…” to indicate there is more code above and/or below that snippet. At the end of the article I will post the entire code for the App.js file.

The first two line of code will be the following:

import React, { Component } from ‘react’;
import { StyleSheet, Text, View } from ‘react-native’;

The second line is important to us because this is where we will be importing all of the built in components. The StyleSheet allows us to create a specific set of styles for our components, Text component allows us to create text and the View component is one of the fundamental components for the user interface. To learn more about this components and the others provided by React Native check out this link, https://facebook.github.io/react-native/docs/components-and-apis.

Next, create a variable named styles. This is where we will create a StyleSheet and style our components.

const styles = StyleSheet.create({

)};

Time to create the class and export it. Here is how the code will look:

class App extends Component {

}
export default App;

Inside the class, we need to add the render function and return our View component. Save the file.

class App extends Component {
   render() {
     return (
     <View />
     )
   }
}

At this point we can run the app. As I mentioned before I am using the project we created with the React Native CLI, therefore I will open a Terminal on my Mac and head to the directory where the project is located and enter the following command:

react-native run-ios

If you are using Windows or simply want to test on Android, type the following into the Terminal or Command Prompt:

react-native run-android

If you decided to work with the Expo project, type the following into the Terminal or Command Prompt and use one of the methods from the previous article to run the app on your phone or simulator/emulator:

expo start

Again, I am using a Mac and for the time being will be using the iOS simulator. Later on I will use the Android emulator to point out some styling differences.

What you will see when the app is up and running is a white, blank screen. Here is the app running on the iOS simulator:

A blank, white screen looks dull, let’s change that by adding some color to the background:

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#bff0d4'
  }
});

class App extends Component {
   render() {
     return (
     <View style={styles.container} />
   )
   }
}

First we add a style prop to the View component. In styles we create a style “container” and gave it a flex of 1 and a background color. “flex: 1”, simply tells the View component to take up all the space available. To learn more about flex, go here https://facebook.github.io/react-native/docs/height-and-width. The color give to the “backgroundColor” can be written in a few ways for React Native, here is a link to learn more, https://facebook.github.io/react-native/docs/colors. If you’ve ever done web development and used CSS, the way to style these components will look familiar. Save the file and if you don’t see the change, reload the simulator/emulator. You can reload by pressing the “Command” and “R” keys for the iOS simulator. For Android emulator, tap “R” twice.

Ok now that we have some color, why not add an image to our app. First thing you need is an image. I found one online of a raccoon, download it and put in a new folder I created inside the project called “img”. But you can also use different type of images, such as using an URL for an image found online. Some of types of images need to have a specific height and width passed to it. Learn more here, https://facebook.github.io/react-native/docs/image. Let’s import the Image component.

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

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#bff0d4',
    alignItems: 'center',
  }
});

class App extends Component {
  render() {
    return (
    <View style={styles.container}>
    <Image resizeMode='center' source={require('./img/raccoon.png')} />
    </View>
    )
  }
}

Inside the “container” style, I added “alignItems: ‘center’” which centers all the components inside of View horizontally. Next, inside the View component I added the Image component and gave the source the location to the image of the raccoon I am using. I also added “resizeMode=’center’” because the image was large and not displaying properly, this will center the image in the view. Here is what I end up with:

You see that the image is still too large, so we are going to change that.

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#bff0d4',
    alignItems: 'center',
  },
  image: {
    height: 200,
    width: 200
  }
});

class App extends Component {
  render() {
    return (
      <View style={styles.container}>
      <Image style={styles.image} resizeMode='contain' source={require('./img/raccoon.png')} />
      </View>
    )
  }
}

Create a new style named “image” and give it a height and width. Pass the style to the Image component and change the “resizeMode” to “contain”. This will ensure the image is resized properly to fit the height and width given.

You will notice the image is too close to the top of the screen and a piece of the ears are cut off. To fix this, add “marginTop” and give it a value of 100. You can play around with the number to get the desired spacing between the top of the screen and the image.

image: {
  height: 200,
  width: 200,
  marginTop: 100
}

Here is what we have:

Inside the View component and right below the Image component, lets add a Text component with the following text, “How many raccoons did you see last night?”.

return (
  <View style={styles.container}>
  <Image style={styles.image} resizeMode='contain' source={require('./img/raccoon.png')} />
  <Text>How many raccoons did you see last night?</Text>
  </View>
)

The text will appear right below the image but it needs some styling. Create a new style named “question” and give it a font size of 20, make it bold, center and change the color. Don’t forget to pass the prop in the Text component.

<Text style={styles.question}>How many raccoons did you see last night?</Text>
question: {
   fontSize: 20,
   fontWeight: 'bold',
   textAlign: 'center',
   color: '#535B60'
}

Looking much better. Next add a new Text component underneath the question which will track the number of raccoons you see. The styling will be similar to that of the question but the font size will be much bigger.

<Text style={styles.number} >0</Text>
number: {
   fontSize: 60,
   fontWeight: 'bold',
   textAlign: 'center',
   color: '#535B60'
}

Here is what we now have:

When we see a raccoon, we will need to be able to press a button which will increase the number or decrease the number. React Native comes with a built in component called Button and we will use it to create a button for increasing the value. Later on, we will use another component that allows for more customization.

import { Button, Image, StyleSheet, Text, View } from 'react-native';
<View style={styles.container}>
  <Image style={styles.image} resizeMode='contain' source={require('./img/raccoon.png')} />
  <Text style={styles.question} >How many raccoons did you see last night?</Text>
  <Text style={styles.number}>0</Text>
  <Button title='PLUS' color='#535B60' />
</View>

Let’s add the Button component right after the Text component display the number. We pass it a title, “PLUS”, and a color. At the moment our button does not do anything. One thing about the Button component is that they look different on iOS than they do on Android. I am going to run the Android emulator and show you the difference.

Here is the button on Android:

And here is the button on iOS:

Notice how on Android the button has “PLUS” in a white font and is surrounded by the color we provided. But on iOS only the text “PLUS” appears and it is in the color we provided. This is something you will notice when working with React Native and you will need to style your components accordingly.

Before telling the button what to do every time it is pressed, lets create a state that will store our number data. Currently, the number of raccoons seen is set to zero but we want it to change with the pressing of the button.

Create a state with “raccoons” set to zero inside of the class App and before the render function. Then use the data to replace the hard coded zero in the Text component.

class App extends Component {
  state = {
    raccoons: 0
  };

render() {
  return (
    <View style={styles.container}>
    <Image style={styles.image} resizeMode='contain' source={require('./img/raccoon.png')} />
    <Text style={styles.question} >How many raccoons did you see last night?</Text>
    <Text style={styles.number}>{this.state.raccoons}</Text>

Notice how I replaced the “0” in the second Text component with “{this.state.raccoons}”, this calls “raccoons” in the state. Change the “0” in the state to another number, save, and see that it changes.

We’re now ready to make our button do something. Create a function called “addMore” and set “raccoons” to increase by 1. Then in the Button component, add the prop “onPress” and pass it the “addMore” function.

addMore = () => {
  this.setState({
    raccoons: this.state.raccoons + 1
  })
}
<Button onPress={this.addMore} title='PLUS' color='#535B60' />

Save the file and see that when you press the button, the counter will increase by one. Here is how it will look:

We can increase the counter but what if we made a mistake and want to decrease the counter? Well, we can create another button that will do just that. This time we will use TouchableOpacity component to create a custom button. Begin by importing TouchableOpacity and adding the component right after the Button component we have in the class. We will wrap the TouchableOpacity around a Text component.

import { Button, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
<Button onPress={this.addMore} title='PLUS' color='#535B60' />

<TouchableOpacity>
<Text>MINUS</Text>
</TouchableOpacity>

Save the file and see that the button needs some styling. We will add styling to the text to change the font size but also to the TouchableOpacity component to make it stand out.

button: {
  backgroundColor: '#BAAAC4',
  width: 200,
  borderRadius: 10
},
buttonText: {
  fontSize: 40,
  fontWeight: 'bold',
  textAlign: 'center',
  color: '#535B60'
}
<TouchableOpacity style={styles.button}>
<Text style={styles.buttonText}>MINUS</Text>
</TouchableOpacity>

We will give the TouchableOpacity component a background color, width of 200 and border radius of 10 to round the corners. To the Text, same as before, font size of 40, bold, align it to the center and give a color. Here is how it will look:

The “Minus” button currently does nothing. We will create a function and pass it to the TouchableOpacity component, just like we did to the Button component.

removeOne = () => {
  this.setState({
    raccoons: this.state.raccoons - 1
  })
}
<TouchableOpacity onPress={this.removeOne} style={styles.button}>
<Text style={styles.buttonText}>MINUS</Text>
</TouchableOpacity>

Test the app! You can add to the counter and remove from it as well. The “MINUS” button is working but it should stop when it reaches zero, we can’t see a negative number of raccoons. This can be easily fixed by adding an if statement to check the current value of “raccoons”.

removeOne = () => {
  if(this.state.raccoons !== 0){
    this.setState({
      raccoons: this.state.raccoons - 1
    })
  }
}

Before calling this app complete, let’s make the “PLUS” button look like the “MINUS” button and do some other styling changes.

import React, { Component } from 'react';
import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#bff0d4',
    alignItems: 'center',
  },
  image: {
    height: 200,
    width: 200,
    marginTop: 100,
    marginBottom: 20
  },
  question: {
    fontSize: 30,
    fontWeight: 'bold',
    textAlign: 'center',
    color: '#535B60',
    padding: 20
  },
  number: {
    fontSize: 60,
    fontWeight: 'bold',
    textAlign: 'center',
    color: '#535B60',
    padding: 20
  },
  plusButton: {
    backgroundColor: '#9FC4AD',
    width: 200,
    borderRadius: 10,
    margin: 10
  },
  minusButton: {
    backgroundColor: '#BAAAC4',
    width: 200,
    borderRadius: 10,
    margin: 10
  },
  buttonText: {
    fontSize: 40,
    fontWeight: 'bold',
    textAlign: 'center',
    color: '#535B60'
  }
});

class App extends Component {
  state = {
    raccoons: 0
  };

  addMore = () => {
    this.setState({
      raccoons: this.state.raccoons + 1
    })
  }
  removeOne = () => {
    if(this.state.raccoons !== 0){
      this.setState({
        raccoons: this.state.raccoons - 1
      })
    }
}

  render() {
    return (
      <View style={styles.container}>
      <Image style={styles.image} resizeMode='contain' source={require('./img/raccoon.png')} />
      <Text style={styles.question} >How many raccoons did you see last night?</Text>

      <Text style={styles.number}>{this.state.raccoons}</Text>

      <TouchableOpacity onPress={this.addMore} style={styles.plusButton} >
      <Text style={styles.buttonText}>PLUS</Text>
      </TouchableOpacity>

      <TouchableOpacity onPress={this.removeOne} style={styles.minusButton}>
      <Text style={styles.buttonText}>MINUS</Text>
      </TouchableOpacity>
      </View>
    )
  }
}

export default App;

First, replace the Button component with a TouchableOpacity component wrapped around a Text component. Pass the “buttonText” style to the Text and the “addMore” function to the TouchableOpacity. Next I wanted to differentiate the two buttons, so I created another style named “plusButton” and renamed “button” to “minusButton”, then passed them accordingly. I then changed the font size of the question and added padding and margin to some components to separate them. Here is what I was left with:

I’m not much of a designer but I am happy with the way the project came out but if there’s something you don’t like, play around with the styling. Adjust the font sizes, font color, background colors or spacing between components. There’s a lot you can do to make your app unique. You can change the image of the raccoon to something else, maybe a bird, or change the color scheme. There are a lot of options online to find royalty free images and color schemes.

In the next article, we will continue to work with this project. We will use more built in components and style them. For now, make sure you understand what was done in this article and if there’s something that doesn’t make sense visit the React Native documentation, https://facebook.github.io/react-native/.