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:

  1. Create the basic UI with a "Take Photo" button
  2. Install expo-camera and request camera permissions
  3. Display the camera view when the button is pressed
  4. Add a capture button to snap photos
  5. 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.

Similar Posts