How to Create a Camera App with Expo and React Native
React Native is a powerful framework that allows you to build native mobile apps using JavaScript and React. However, setting up and configuring a React Native development environment can be complex and time-consuming, especially for beginners.
This is where Expo comes in. Expo is a set of tools and services built around React Native that helps you quickly bootstrap and develop React Native apps with less complexity. With Expo, you can build, deploy, and quickly iterate on iOS, Android, and web apps from the same JavaScript codebase.
In this tutorial, we‘ll walk through how to create a camera app in React Native using the Expo framework. The app will allow users to take photos, preview the images, save or retake them, and toggle features like flash and switching cameras. We‘ll cover everything you need to know to get started.
Prerequisites
Before we dive in, make sure you have the following installed:
- Node.js (version 12 or later)
- Expo CLI
You can install the Expo CLI by running:
npm install -g expo-cli
You‘ll also need either an iOS or Android device to run the app. If you don‘t have a device handy, you can use an emulator/simulator, although we recommend testing on a physical device for the camera functionality.
For the best development experience, install the Expo client app on your device, available on the App Store and Google Play. This will allow you to open the app directly on your phone.
Getting Started
With the prerequisites out of the way, let‘s initialize a new Expo project. Run the following command:
expo init camera-app
Select the "blank" template when prompted. This will create a new directory called camera-app
with the following structure:
camera-app
├── assets
├── node_modules
├── .gitignore
├── App.js
├── app.json
├── babel.config.js
├── package.json
└── README.md
The App.js
file is the main entry point for the application. This is where we‘ll write the code for our camera app.
To start the development server, navigate to the project directory and run:
cd camera-app
expo start
This will start Metro Bundler and output a QR code in the terminal. Open the Expo client app on your device and scan the QR code to launch the app. You should see a basic "Hello World" screen.
Building the Camera App
Now let‘s start building out the camera functionality. We‘ll break it down into a few key steps:
- Create the basic UI with a "Take Photo" button
- Install
expo-camera
and request camera permissions - Display the camera view when the button is pressed
- Add a capture button to snap photos
- Show a preview of the photo with the option to save or retake
Step 1: Creating the Basic UI
Replace the contents of App.js
with the following code:
import React from ‘react‘;
import { StyleSheet, Text, View, Button } from ‘react-native‘;
export default function App() {
return (
<View style={styles.container}>
<Button title="Take Photo" onPress={() => {}} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: ‘#fff‘,
alignItems: ‘center‘,
justifyContent: ‘center‘,
},
});
This sets up a basic screen with a centered "Take Photo" button that doesn‘t do anything yet. We‘ll wire it up in the next step.
Step 2: Installing expo-camera and Requesting Permissions
To access the device‘s camera, we‘ll need to install the expo-camera
package. Run the following in your project directory:
expo install expo-camera
Next, we need to request permission to access the camera. Add the following code above the App
component:
import * as Permissions from ‘expo-permissions‘;
async function getCameraPermission() {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
return status === ‘granted‘;
}
This uses the Permissions
API from expo-permissions
to request camera access. We‘ll call this function when the user presses the "Take Photo" button.
Step 3: Displaying the Camera View
Now let‘s display the actual camera view when the user grants permission. First, add a state variable to keep track of whether the user has given permission:
const [hasCameraPermission, setHasCameraPermission] = React.useState(null);
Then update the button‘s onPress
handler to request permission and update the state:
<Button
title="Take Photo"
onPress={async () => {
const hasPerm = await getCameraPermission();
setHasCameraPermission(hasPerm);
}}
/>
Finally, add the camera view below the button:
{hasCameraPermission ? (
<Camera style={{ flex: 1 }} type={Camera.Constants.Type.back} />
) : undefined}
This conditionally renders the Camera
component from expo-camera
if the user has granted permission. We‘ve specified the camera type
to use the rear-facing camera.
Step 4: Adding a Capture Button
Let‘s add a button to actually capture a photo. First, create a reference to the camera instance:
const cameraRef = React.useRef(null);
Then add a "capture" button below the camera view:
<View style={styles.buttonContainer}>
<Button title="Capture" onPress={async () => {
if (cameraRef.current) {
const options = { quality: 0.5, base64: true };
const data = await cameraRef.current.takePictureAsync(options);
console.log(data.uri);
}
}} />
</View>
And the corresponding styles:
const styles = StyleSheet.create({
// ...
buttonContainer: {
position: ‘absolute‘,
bottom: 0,
flexDirection: ‘row‘,
flex: 1,
width: ‘100%‘,
padding: 20,
justifyContent: ‘space-between‘
}
});
This uses the takePictureAsync
method from the camera reference to capture a photo. We‘ve specified some options to reduce the image quality (for performance) and include base64 encoding.
When a photo is taken, the data.uri
contains the local path where the image is stored. We‘ll use this in the next step to preview the image.
Step 5: Previewing the Photo
After capturing a photo, we want to show a preview of it with the option to save or retake the image. Add a state variable to store the image data:
const [capturedImage, setCapturedImage] = React.useState(null);
Then update the Capture
button‘s onPress
handler to store the image in state:
const data = await cameraRef.current.takePictureAsync(options);
setCapturedImage(data.uri);
Next, add a new component to display the image preview:
function ImagePreview({ photo, retakePicture, savePhoto }) {
return (
<View style={styles.imagePreview}>
<Image source={{ uri: photo }} style={{ flex: 1 }} />
<View style={styles.previewButtons}>
<Button title="Re-take" onPress={retakePicture} />
<Button title="Save" onPress={savePhoto} />
</View>
</View>
);
}
And conditionally render it based on whether an image has been captured:
{capturedImage ? (
<ImagePreview
photo={capturedImage}
retakePicture={() => setCapturedImage(null)}
savePhoto={() => console.log(‘Saving photo‘)}
/>
) : (
<Camera style={{ flex: 1 }} type={Camera.Constants.Type.back} ref={cameraRef}>
{/* ... */}
</Camera>
)}
The "Re-take" button discards the captured image and goes back to the camera view. The "Save" button would save the image to the device‘s storage (we‘ll leave that as an exercise to the reader).
Adding Extra Features
Our basic camera app is working now, but let‘s add a couple of extra features to round it out.
Enabling Flash
To use the flash, add a new state variable:
const [hasFlash, setHasFlash] = React.useState(null);
Then check if the device has a flash available in the useEffect
hook:
React.useEffect(() => {
(async () => {
const { status } = await Camera.requestPermissionsAsync();
setHasCameraPermission(status === ‘granted‘);
const flashAvailable = await Camera.getAvailableCameraTypesAsync();
setHasFlash(flashAvailable.includes(Camera.Constants.FlashMode.torch));
})();
}, []);
Next, add a button to toggle the flash:
<View style={styles.flashButton}>
<Button
title={flashEnabled ? ‘Flash: On‘ : ‘Flash: Off‘}
onPress={() => setFlashEnabled(!flashEnabled)}
disabled={!hasFlash}
/>
</View>
And update the Camera
component to use the flash setting:
<Camera
flashMode={flashEnabled ? Camera.Constants.FlashMode.torch : Camera.Constants.FlashMode.off}
{/* ... */}
/>
Switching Cameras
To switch between front and rear-facing cameras, add a state variable:
const [isFrontCamera, setIsFrontCamera] = React.useState(false);
Then add a button to toggle it:
<Button
title={isFrontCamera ? ‘Front Camera‘ : ‘Back Camera‘}
onPress={() => setIsFrontCamera(!isFrontCamera)}
/>
And update the camera type
prop based on the state:
<Camera
type={isFrontCamera ? Camera.Constants.Type.front : Camera.Constants.Type.back}
{/* ... */}
/>
We could also add other features like zooming, focusing, or adding image overlays, but we‘ll leave those as exercises for the reader to explore.
Conclusion
In this tutorial, we‘ve learned how to build a basic camera app in React Native using the Expo framework. We covered how to:
- Set up a new Expo project
- Install
expo-camera
and request camera permissions - Capture, preview, and save photos
- Enable camera features like flash and switching cameras
Expo abstracts away much of the complex native code and configuration required for tasks like using the camera, allowing us to focus on the core logic of our app. The trade-off is less flexibility compared to the bare React Native CLI, which allows full control over native code.
When deciding between Expo and vanilla React Native, consider the complexity of your app and how much you need to customize the native functionality. Expo is great for quickly bootstrapping and deploying apps, while React Native CLI provides the escape hatch to tweak the underlying native code when needed.
You can view the full source code for this tutorial on GitHub.
I hope this tutorial has been helpful in getting you started with React Native camera functionality using Expo! Let me know if you have any questions or feedback.