Skip to main content
TASIOMIND.DEV — OPERATIONAL▸▸▸FULL STACK DEVELOPER @ GWQ SERVICEPLUS AG▸▸▸FOUNDER — K8SGPT.AI▸▸▸OPEN SOURCE: ACTIVE▸▸▸DISTRIBUTED SYSTEMS / KUBERNETES / AI▸▸▸RUST + GO + PYTHON▸▸▸FIELD TESTED / STATUS — NOMINAL▸▸▸LOCATION: EUROPE/BERLIN▸▸▸TASIOMIND.DEV — OPERATIONAL▸▸▸FULL STACK DEVELOPER @ GWQ SERVICEPLUS AG▸▸▸FOUNDER — K8SGPT.AI▸▸▸OPEN SOURCE: ACTIVE▸▸▸DISTRIBUTED SYSTEMS / KUBERNETES / AI▸▸▸RUST + GO + PYTHON▸▸▸FIELD TESTED / STATUS — NOMINAL▸▸▸LOCATION: EUROPE/BERLIN▸▸▸
expo

How to Upload an Image to Cloudinary using Expo Camera

Aland Baban · March 2021 · 5 min read
[ contents ]

The camera is a fundamental feature of modern mobile devices, enabling applications to capture images and record videos. The expo-camera library simplifies the integration of this functionality into React Native applications built with Expo.

This tutorial demonstrates how to use the Expo Camera API to take a picture and subsequently upload it to Cloudinary, a cloud-based image and video management service.

Prerequisites

To follow this tutorial, please ensure you are familiar with JavaScript/ES6 and meet the following requirements in your local development environment:

  • Node.js version >= 14.x.x installed.
  • Access to a package manager such as npm, yarn, or npx.
  • expo-cli installed globally, or use npx for local execution.

The source code for this tutorial is available at this Github repository.

Create an Expo App

Begin by creating a new Expo project and installing the expo-camera dependency. Execute the following commands in a terminal window:

code
npx create-expo-app project-name

# Select the blank template when prompted
cd project-name

npx expo install expo-camera

Create a Custom Camera Component

The expo-camera library provides a Camera React component that facilitates taking pictures and recording videos. It exposes numerous properties, including zoom, autofocus, white balance, and flash mode.

For this guide, we will create a component that renders the Camera in full-screen mode. Start by adding the following imports to your App.js file:

code
import React, { useState, useRef, useEffect } from 'react';
import {
  StyleSheet,
  Dimensions,
  View,
  Text,
  TouchableOpacity
} from 'react-native';
import { Camera } from 'expo-camera';
import { AntDesign, MaterialIcons } from '@expo/vector-icons';

We'll use the Dimensions API from React Native to get the device's screen height, which will help in styling the camera view.

code
const WINDOW_HEIGHT = Dimensions.get('window').height;
const CAPTURE_SIZE = Math.floor(WINDOW_HEIGHT * 0.08);

To invoke methods on the Camera component, we need a reference to it using the useRef hook.

code
export default function App() {
  const cameraRef = useRef();
  // ... other state and functions
  return (
    <View style={styles.container}>
      <Camera ref={cameraRef} style={styles.container} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    ...StyleSheet.absoluteFillObject
  }
});

Checking for Camera Permissions

To use the device's camera, your application must request permission from the user. We will handle this using a state variable and an effect.

  • Define a state variable hasPermission using useState.
  • Create an asynchronous function onHandlePermission that calls Camera.requestPermissionsAsync() and updates the state based on the result.
  • Invoke this function within a useEffect hook when the component mounts.
code
export default function App() {
  const cameraRef = useRef();
  const [hasPermission, setHasPermission] = useState(null);

  useEffect(() => {
    onHandlePermission();
  }, []);

  const onHandlePermission = async () => {
    const { status } = await Camera.requestCameraPermissionsAsync();
    setHasPermission(status === 'granted');
  };

  if (hasPermission === null) {
    return <View />;
  }
  if (hasPermission === false) {
    return <Text style={styles.text}>No access to camera</Text>;
  }

  // ... rest of the component
}

The UI will display a loading state until the permission status is determined and a message if permission is denied.

Android Permission Prompt

Switching Between Camera Types

To allow switching between the front and back cameras, we will use another state variable, cameraType, and a function to toggle its value.

  • Define cameraType with a default value of Camera.Constants.Type.back.
  • Define isPreview to track whether the app is showing the camera view or a captured image preview.
  • Create a switchCamera function to toggle the cameraType state.
code
export default function App() {
  // ... existing state and hooks
  const [cameraType, setCameraType] = useState(Camera.Constants.Type.back);
  const [isPreview, setIsPreview] = useState(false);

  const switchCamera = () => {
    if (isPreview) {
      return;
    }
    setCameraType(prevCameraType =>
      prevCameraType === Camera.Constants.Type.back
        ? Camera.Constants.Type.front
        : Camera.Constants.Type.back
    );
  };
  
  // ...
  
  return (
    <View style={styles.container}>
      <Camera
        ref={cameraRef}
        style={styles.container}
        type={cameraType}
        // ... other props
      />
      <View style={styles.container}>
        {!isPreview && (
          <View style={styles.bottomButtonsContainer}>
            <TouchableOpacity disabled={!isCameraReady} onPress={switchCamera}>
              <MaterialIcons name="flip-camera-ios" size={28} color="white" />
            </TouchableOpacity>
            {/* ... other buttons */}
          </View>
        )}
      </View>
    </View>
  );
}

Taking and Previewing a Picture

The takePictureAsync() method from the Camera API captures an image and saves it to the app's cache. We will use the base64 option to get the image data directly.

  • Create an onSnap async function that calls cameraRef.current.takePictureAsync().
  • If the picture is taken successfully, pause the camera preview and set isPreview to true.
code
const onSnap = async () => {
  if (cameraRef.current) {
    const options = { quality: 0.7, base64: true };
    const data = await cameraRef.current.takePictureAsync(options);
    const source = data.base64;

    if (source) {
      await cameraRef.current.pausePreview();
      setIsPreview(true);
      // We will handle the upload here later
    }
  }
};

Create a button to trigger onSnap and another to cancel the preview and return to the camera view.

code
const cancelPreview = async () => {
  await cameraRef.current.resumePreview();
  setIsPreview(false);
};

The UI should conditionally render the capture button or the preview-cancel button based on the isPreview state.

Image Preview

Setting up Cloudinary

To upload the image, you need a Cloudinary account. From your dashboard, you will need two pieces of information:

  1. Cloud Name: Found on your dashboard.
  2. Upload Preset: Create a new unsigned upload preset in Settings > Upload.

Create Upload Preset

Uploading the Image

We will use the fetch API to send a POST request to the Cloudinary API endpoint. The body of the request will contain the base64 image data and your upload preset name.

Update the onSnap function to include the upload logic.

code
const onSnap = async () => {
  if (cameraRef.current) {
    const options = { quality: 0.7, base64: true };
    const data = await cameraRef.current.takePictureAsync(options);
    const source = data.base64;

    if (source) {
      await cameraRef.current.pausePreview();
      setIsPreview(true);

      let base64Img = `data:image/jpg;base64,${source}`;
      let apiUrl = 'https://api.cloudinary.com/v1_1/YOUR_CLOUD_NAME/image/upload';
      let data = {
        "file": base64Img,
        "upload_preset": "YOUR_UPLOAD_PRESET",
      };

      fetch(apiUrl, {
        body: JSON.stringify(data),
        headers: {
          'content-type': 'application/json'
        },
        method: 'POST',
      }).then(async r => {
          let data = await r.json()
          if(data.secure_url){
            alert("Upload successful");
          }
        }).catch(err => alert("Cannot upload"));
    }
  }
};

Remember to replace YOUR_CLOUD_NAME and YOUR_UPLOAD_PRESET with your actual Cloudinary credentials.

Upload Success

Using Camera2 API for Android

To use Android's modern camera2 API for better performance and features, add the useCamera2Api prop to the Camera component.

code
<Camera
  // ...
  useCamera2Api={true}
/>

Conclusion

This tutorial covered the essentials of using the Expo Camera library to capture an image and upload it to a cloud service like Cloudinary. For saving images to the device's gallery, consider using the expo-media-library.