Paramclasses Academy - Blog

Tutorials Details

←
Post 26 of 39
→

How to Use Camera and Gallery in a React Native App ?

How to Use Camera and Gallery in a React Native App ?

November 5, 2026

1. Introduction

Modern mobile applications frequently use the device camera and photo gallery.

For example:

  • Profile photo upload applications.

  • Student ID card applications.

  • Document scanning applications.

  • Social media applications.

  • Product image upload applications.

  • Attendance applications with photo capture.

React Native provides libraries that allow applications to access these device features.

In this tutorial, we will use two Expo libraries:

  1. expo-image-picker – Open the camera interface or select images from the device gallery.

  2. expo-camera – Build a custom camera screen with a live camera preview.

We will first build a complete Photo Capture and Gallery App, then understand how to create a custom camera interface.

2. Understanding Camera and Gallery Integration

Before writing code, understand the basic workflow.

Camera Workflow

  1. User presses the Take Photo button.

  2. Application requests camera permission if needed.

  3. The device camera opens.

  4. User captures a photograph.

  5. The camera returns the image URI.

  6. React Native displays the selected photograph.

Gallery Workflow

  1. User presses the Choose from Gallery button.

  2. The system photo picker opens.

  3. User selects an image.

  4. The image picker returns the selected image URI.

  5. React Native displays the image.

Required Libraries

Library Purpose
expo-image-picker Capture photos using the system camera UI and select gallery images
expo-camera Build a custom camera preview and capture interface
expo-image Optional image rendering library

For this practical project, we will use React Native's built-in Image component, so expo-image is not required.

3. Create a New React Native Project

Open your terminal.

Create an Expo project:

npx create-expo-app@latest PhotoGalleryApp --template blank

Open the project:

cd PhotoGalleryApp

Start the project once:

npx expo start

You can test the application using Expo Go on a physical Android device.

4. Install Required Packages

Stop the development server using:

Ctrl + C

Install the image picker library:

npx expo install expo-image-picker

This package provides access to the system interface for selecting images and launching the camera.

Restart the development server:

npx expo start

5. Understanding Permissions

Camera and photo library access are protected by the mobile operating system.

Applications must respect the permission system and explain why access is needed.

Camera Permission

The application needs camera permission before launching the device camera.

Gallery Permission

The application may need photo library access depending on the platform and picker operation.

Modern system photo pickers can provide access to selected images without requiring broad access to the entire photo library.

Permission Example

import * as ImagePicker from 'expo-image-picker';

const permission =
  await ImagePicker.requestCameraPermissionsAsync();

if (permission.granted) {
  console.log('Camera permission granted');
} else {
  console.log('Camera permission denied');
}

For gallery access:

const permission =
  await ImagePicker.requestMediaLibraryPermissionsAsync();

if (permission.granted) {
  console.log('Gallery permission granted');
} else {
  console.log('Gallery permission denied');
}

Important: Do not request permissions unnecessarily. Request access when the user starts the feature that needs it, and handle denied permissions gracefully.

6. Understanding expo-image-picker

The expo-image-picker package provides two important methods.

A. launchCameraAsync()

This method launches the system camera interface.

const result = await ImagePicker.launchCameraAsync({
  mediaTypes: ['images'],
  allowsEditing: true,
  quality: 1,
});

B. launchImageLibraryAsync()

This method opens the device image library.

const result = await ImagePicker.launchImageLibraryAsync({
  mediaTypes: ['images'],
  allowsEditing: true,
  quality: 1,
});

Understanding the Result

When the user successfully selects or captures an image, the result contains an assets array.

Example:

if (!result.canceled) {
  const imageUri = result.assets[0].uri;

  console.log(imageUri);
}

The URI identifies the selected image so that the application can display or process it.

If the user cancels the picker, result.canceled is true.

7. Practical Project: Photo Capture and Gallery App

Now we will create a complete application with these features:

  • Capture a photograph using the camera.

  • Select an image from the gallery.

  • Display the selected image.

  • Show image information.

  • Remove the selected image.

  • Handle permission errors and cancellation.

Step 1: Project Structure

Our project will use the following structure:

PhotoGalleryApp/
│
├── App.js
│
├── package.json
│
└── assets/

We will write the main application inside App.js.

Step 2: Import Required Components

Open App.js and add:

import React, { useState } from 'react';

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

import * as ImagePicker from 'expo-image-picker';

Explanation:

  • useState stores the selected image.

  • View creates the application layout.

  • Text displays information.

  • Button creates action buttons.

  • Image displays the selected photograph.

  • Alert displays messages.

  • ScrollView makes the screen scrollable.

  • ImagePicker provides camera and gallery functions.

Step 3: Create the Main Component

export default function App() {
  const [image, setImage] = useState(null);
  const [imageInfo, setImageInfo] = useState(null);

  return (
    <View style={styles.container}>
      <Text style={styles.heading}>
        Photo Capture App
      </Text>

      <Text style={styles.subtitle}>
        Capture a photo or choose one from your gallery.
      </Text>
    </View>
  );
}

Here, we created two state variables:

  • image stores the image URI.

  • imageInfo stores image details.

Step 4: Create the Camera Function

Inside the App component, add:

const takePhoto = async () => {
  try {
    const permission =
      await ImagePicker.requestCameraPermissionsAsync();

    if (!permission.granted) {
      Alert.alert(
        'Permission Required',
        'Please allow camera access to take a photo.'
      );

      return;
    }

    const result = await ImagePicker.launchCameraAsync({
      mediaTypes: ['images'],
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
    });

    if (!result.canceled) {
      const selectedAsset = result.assets[0];

      setImage(selectedAsset.uri);

      setImageInfo({
        fileName: selectedAsset.fileName,
        width: selectedAsset.width,
        height: selectedAsset.height,
        fileSize: selectedAsset.fileSize,
      });
    }
  } catch (error) {
    Alert.alert('Error', 'Unable to open the camera.');
  }
};

How does this function work?

  1. It requests camera permission.

  2. It checks whether permission is granted.

  3. It opens the system camera interface.

  4. The user captures a photograph.

  5. The result returns the captured image.

  6. setImage() stores the image URI.

  7. setImageInfo() stores the available image details.

Step 5: Create the Gallery Function

Add another function:

const chooseFromGallery = async () => {
  try {
    const result =
      await ImagePicker.launchImageLibraryAsync({
        mediaTypes: ['images'],
        allowsEditing: true,
        aspect: [4, 3],
        quality: 1,
      });

    if (!result.canceled) {
      const selectedAsset = result.assets[0];

      setImage(selectedAsset.uri);

      setImageInfo({
        fileName: selectedAsset.fileName,
        width: selectedAsset.width,
        height: selectedAsset.height,
        fileSize: selectedAsset.fileSize,
      });
    }
  } catch (error) {
    Alert.alert(
      'Error',
      'Unable to select an image from the gallery.'
    );
  }
};

How does this function work?

  1. It launches the system image library.

  2. The user selects an image.

  3. The image picker returns the selected asset.

  4. We extract its URI.

  5. We update the image state.

  6. React renders the selected image.

The modern system photo picker may allow users to select specific photos without granting broad library access.

Step 6: Create the Remove Image Function

const removeImage = () => {
  setImage(null);
  setImageInfo(null);

  Alert.alert(
    'Image Removed',
    'The selected image has been removed from the preview.'
  );
};

This function clears the image preview and its information.

It does not delete the original photo from the device.

Step 7: Display the Selected Image

Inside the component's return statement, add the following interface:

return (
  <ScrollView contentContainerStyle={styles.container}>
    <Text style={styles.heading}>
      Photo Capture App
    </Text>

    <Text style={styles.subtitle}>
      Capture a photo or choose one from your gallery.
    </Text>

    <View style={styles.previewContainer}>
      {image ? (
        <Image
          source={{ uri: image }}
          style={styles.previewImage}
          resizeMode="cover"
        />
      ) : (
        <Text style={styles.placeholder}>
          No Image Selected
        </Text>
      )}
    </View>

    <View style={styles.button}>
      <Button
        title="Take Photo"
        onPress={takePhoto}
      />
    </View>

    <View style={styles.button}>
      <Button
        title="Choose from Gallery"
        onPress={chooseFromGallery}
      />
    </View>

    {image && (
      <View style={styles.button}>
        <Button
          title="Remove Image"
          color="red"
          onPress={removeImage}
        />
      </View>
    )}

    {imageInfo && (
      <View style={styles.infoContainer}>
        <Text style={styles.infoHeading}>
          Image Information
        </Text>

        <Text>
          File Name: {imageInfo.fileName || 'Unavailable'}
        </Text>

        <Text>
          Width: {imageInfo.width || 'Unavailable'} px
        </Text>

        <Text>
          Height: {imageInfo.height || 'Unavailable'} px
        </Text>

        <Text>
          File Size: {
            imageInfo.fileSize
              ? `${(imageInfo.fileSize / 1024).toFixed(2)} KB`
              : 'Unavailable'
          }
        </Text>
      </View>
    )}
  </ScrollView>
);

Step 8: Add Application Styling

Add these styles at the bottom of App.js:

const styles = StyleSheet.create({
  container: {
    flexGrow: 1,
    backgroundColor: '#f1f5f9',
    padding: 20,
    alignItems: 'center',
    paddingTop: 55,
  },

  heading: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#1e3a8a',
    marginBottom: 10,
  },

  subtitle: {
    fontSize: 15,
    color: '#64748b',
    textAlign: 'center',
    marginBottom: 25,
  },

  previewContainer: {
    width: '100%',
    height: 280,
    backgroundColor: '#e2e8f0',
    borderRadius: 15,
    borderWidth: 1,
    borderColor: '#cbd5e1',
    justifyContent: 'center',
    alignItems: 'center',
    overflow: 'hidden',
    marginBottom: 25,
  },

  previewImage: {
    width: '100%',
    height: '100%',
  },

  placeholder: {
    fontSize: 18,
    color: '#64748b',
  },

  button: {
    width: '100%',
    marginBottom: 15,
  },

  infoContainer: {
    width: '100%',
    backgroundColor: '#ffffff',
    padding: 15,
    borderRadius: 12,
    marginTop: 15,
    gap: 8,
  },

  infoHeading: {
    fontSize: 19,
    fontWeight: 'bold',
    marginBottom: 8,
    color: '#1e3a8a',
  },
});

8. Complete App.js Code

For convenience, here is the complete code in one place.

import React, { useState } from 'react';

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

import * as ImagePicker from 'expo-image-picker';

export default function App() {
  const [image, setImage] = useState(null);
  const [imageInfo, setImageInfo] = useState(null);

  const takePhoto = async () => {
    try {
      const permission =
        await ImagePicker.requestCameraPermissionsAsync();

      if (!permission.granted) {
        Alert.alert(
          'Permission Required',
          'Please allow camera access to take a photo.'
        );

        return;
      }

      const result = await ImagePicker.launchCameraAsync({
        mediaTypes: ['images'],
        allowsEditing: true,
        aspect: [4, 3],
        quality: 1,
      });

      if (!result.canceled) {
        const asset = result.assets[0];

        setImage(asset.uri);

        setImageInfo({
          fileName: asset.fileName,
          width: asset.width,
          height: asset.height,
          fileSize: asset.fileSize,
        });
      }
    } catch (error) {
      Alert.alert('Error', 'Unable to open the camera.');
    }
  };

  const chooseFromGallery = async () => {
    try {
      const result =
        await ImagePicker.launchImageLibraryAsync({
          mediaTypes: ['images'],
          allowsEditing: true,
          aspect: [4, 3],
          quality: 1,
        });

      if (!result.canceled) {
        const asset = result.assets[0];

        setImage(asset.uri);

        setImageInfo({
          fileName: asset.fileName,
          width: asset.width,
          height: asset.height,
          fileSize: asset.fileSize,
        });
      }
    } catch (error) {
      Alert.alert(
        'Error',
        'Unable to select an image from the gallery.'
      );
    }
  };

  const removeImage = () => {
    setImage(null);
    setImageInfo(null);

    Alert.alert(
      'Image Removed',
      'The selected image has been removed from the preview.'
    );
  };

  return (
    <ScrollView contentContainerStyle={styles.container}>
      <Text style={styles.heading}>
        Photo Capture App
      </Text>

      <Text style={styles.subtitle}>
        Capture a photo or choose one from your gallery.
      </Text>

      <View style={styles.previewContainer}>
        {image ? (
          <Image
            source={{ uri: image }}
            style={styles.previewImage}
            resizeMode="cover"
          />
        ) : (
          <Text style={styles.placeholder}>
            No Image Selected
          </Text>
        )}
      </View>

      <View style={styles.button}>
        <Button title="Take Photo" onPress={takePhoto} />
      </View>

      <View style={styles.button}>
        <Button
          title="Choose from Gallery"
          onPress={chooseFromGallery}
        />
      </View>

      {image && (
        <View style={styles.button}>
          <Button
            title="Remove Image"
            color="red"
            onPress={removeImage}
          />
        </View>
      )}

      {imageInfo && (
        <View style={styles.infoContainer}>
          <Text style={styles.infoHeading}>
            Image Information
          </Text>

          <Text>
            File Name: {imageInfo.fileName || 'Unavailable'}
          </Text>

          <Text>
            Width: {imageInfo.width || 'Unavailable'} px
          </Text>

          <Text>
            Height: {imageInfo.height || 'Unavailable'} px
          </Text>

          <Text>
            File Size: {
              imageInfo.fileSize
                ? `${(imageInfo.fileSize / 1024).toFixed(2)} KB`
                : 'Unavailable'
            }
          </Text>
        </View>
      )}
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    flexGrow: 1,
    backgroundColor: '#f1f5f9',
    padding: 20,
    alignItems: 'center',
    paddingTop: 55,
  },

  heading: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#1e3a8a',
    marginBottom: 10,
  },

  subtitle: {
    fontSize: 15,
    color: '#64748b',
    textAlign: 'center',
    marginBottom: 25,
  },

  previewContainer: {
    width: '100%',
    height: 280,
    backgroundColor: '#e2e8f0',
    borderRadius: 15,
    borderWidth: 1,
    borderColor: '#cbd5e1',
    justifyContent: 'center',
    alignItems: 'center',
    overflow: 'hidden',
    marginBottom: 25,
  },

  previewImage: {
    width: '100%',
    height: '100%',
  },

  placeholder: {
    fontSize: 18,
    color: '#64748b',
  },

  button: {
    width: '100%',
    marginBottom: 15,
  },

  infoContainer: {
    width: '100%',
    backgroundColor: '#ffffff',
    padding: 15,
    borderRadius: 12,
    marginTop: 15,
    gap: 8,
  },

  infoHeading: {
    fontSize: 19,
    fontWeight: 'bold',
    marginBottom: 8,
    color: '#1e3a8a',
  },
});

9. Run and Test the Application

Start the application:

npx expo start

Open the project using Expo Go on your Android phone.

Test 1: Camera

  1. Press Take Photo.

  2. Allow camera permission.

  3. Capture a photograph.

  4. Confirm that the photo appears in the preview.

  5. Check the image information.

Test 2: Gallery

  1. Press Choose from Gallery.

  2. Select a photograph.

  3. Confirm that the image appears in the preview.

  4. Check its dimensions and file information.

Test 3: Remove Image

  1. Press Remove Image.

  2. Confirm that the image disappears.

  3. Confirm that the placeholder appears again.

10. Understanding Custom Camera Preview with expo-camera

So far, we have used the system camera interface through expo-image-picker.

However, some applications require their own camera screen.

Examples include:

  • Custom camera applications.

  • QR code scanners.

  • Document scanning applications.

  • Attendance photo capture systems.

  • Selfie applications.

For this purpose, Expo provides the expo-camera package.

Step 1: Install expo-camera

npx expo install expo-camera

Step 2: Import Camera Components

import {
  CameraView,
  useCameraPermissions,
} from 'expo-camera';

import React, { useRef, useState } from 'react';

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

Step 3: Create a Custom Camera Screen

export default function CustomCamera() {
  const cameraRef = useRef(null);

  const [permission, requestPermission] =
    useCameraPermissions();

  const [photo, setPhoto] = useState(null);

  const [facing, setFacing] = useState('back');

  if (!permission) {
    return <View />;
  }

  if (!permission.granted) {
    return (
      <View style={styles.container}>
        <Text>Camera permission is required.</Text>

        <Button
          title="Grant Camera Permission"
          onPress={requestPermission}
        />
      </View>
    );
  }

  const capturePhoto = async () => {
    try {
      const result =
        await cameraRef.current?.takePictureAsync();

      if (result?.uri) {
        setPhoto(result.uri);
      }
    } catch (error) {
      Alert.alert('Error', 'Unable to capture photo.');
    }
  };

  return (
    <View style={styles.container}>
      {photo ? (
        <>
          <Image
            source={{ uri: photo }}
            style={styles.preview}
          />

          <Button
            title="Take Another Photo"
            onPress={() => setPhoto(null)}
          />
        </>
      ) : (
        <>
          <CameraView
            ref={cameraRef}
            style={styles.camera}
            facing={facing}
          />

          <Button
            title="Capture Photo"
            onPress={capturePhoto}
          />

          <Button
            title="Flip Camera"
            onPress={() => {
              setFacing(current =>
                current === 'back' ? 'front' : 'back'
              );
            }}
          />
        </>
      )}
    </View>
  );
}

Step 4: Add Custom Camera Styles

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#000',
    justifyContent: 'center',
    alignItems: 'center',
  },

  camera: {
    width: '100%',
    height: '75%',
  },

  preview: {
    width: '90%',
    height: '70%',
    resizeMode: 'contain',
  },
});

This component uses CameraView to show a live camera preview. The capture method returns a photo URI that can be displayed using React Native's Image component.

The facing state allows users to switch between the front and back cameras.

Important: Only one camera preview should be active at a time. Unmount or deactivate the camera when its screen is no longer in use.

11. Difference Between expo-image-picker and expo-camera

Feature expo-image-picker expo-camera
Open system camera Yes No, provides an in-app preview
Select gallery image Yes No
Custom camera interface No Yes
Live camera preview No Yes
Capture photo Yes Yes
Flip camera System interface Application-controlled
QR/barcode scanning No custom preview Supported

Use expo-image-picker when you need a quick camera or gallery selection workflow.

Use expo-camera when you need complete control over the camera interface.

12. Common Errors and Solutions

Error 1: Camera Permission Denied

Reason: The user has not granted camera access.

Solution: Explain why access is needed and provide a button to request permission again. If the operating system no longer displays the permission prompt, the user may need to enable access in device settings.

Error 2: Camera Not Opening in Emulator

Reason: The emulator may not have a properly configured virtual camera.

Solution:

  • Check Android Studio's emulator camera configuration.

  • Test on a physical Android device.

  • Confirm camera permission.

Error 3: Selected Image Is Not Displaying

Reason: The image URI may not be stored correctly.

Solution:

console.log(result.assets[0].uri);

Confirm that the URI is passed correctly:

<Image
  source={{ uri: image }}
  style={{ width: 200, height: 200 }}
/>

Error 4: User Cancels Image Selection

Reason: The user closes the camera or gallery without selecting an image.

Solution:

if (result.canceled) {
  console.log('User cancelled image selection');
  return;
}

Error 5: Image Is Too Large

Reason: High-resolution images can consume significant memory and upload bandwidth.

Solution:

Use suitable image quality settings or resize/compress the image before uploading it to a backend.

13. Important Security and Development Practices

  • Request camera and gallery permissions only when needed.

  • Do not access photos without user consent.

  • Do not assume every device has a working camera.

  • Handle cancellation and permission denial.

  • Validate image file types and sizes on the backend.

  • Use secure HTTPS connections when uploading images.

  • Avoid storing sensitive images in publicly accessible folders.

  • Use proper backend authentication for private image uploads.

  • Remember that a local image URI is not automatically a permanent storage location.

The image URI returned by the picker commonly points to a temporary or app-accessible file. If the image must survive application restarts or be shared with a backend, implement appropriate persistent storage or upload logic.

14. Frequently Asked Questions

Q1. How do we open the camera in React Native?

You can use expo-image-picker with launchCameraAsync() or build a custom camera screen using expo-camera.

Q2. How do we select an image from the gallery?

Use ImagePicker.launchImageLibraryAsync().

Q3. Do we need camera permission?

Yes, camera access requires permission. Gallery permission behavior depends on the platform and the picker interface being used.

Q4. Can we display the selected image in React Native?

Yes. Use the Image component with the returned URI.

Q5. Can we switch between front and back cameras?

Yes. The expo-camera library supports camera selection through its facing property.

Q6. Can we upload captured images to PHP and MySQL?

Yes. You can upload an image to a PHP backend using FormData and an HTTP request. The backend should validate the file and store it safely. MySQL can store the image path or URL rather than the image binary in most ordinary applications.

Q7. Can we build a QR code scanner using the camera?

Yes. expo-camera supports barcode detection through its camera preview and barcode scanner settings.

15. Conclusion

In this tutorial, we learned how to integrate camera and gallery functionality into a React Native application.

We covered:

  • Camera and gallery workflows.

  • Installing expo-image-picker.

  • Requesting device permissions.

  • Capturing photographs.

  • Selecting images from the gallery.

  • Displaying selected images.

  • Reading image information.

  • Creating a custom camera screen with expo-camera.

  • Handling common errors and permissions.

These features are useful for building profile upload systems, student registration applications, attendance systems, document scanners, and many other mobile applications.

16. Official Documentation

  1. Expo Image Picker: https://docs.expo.dev/versions/latest/sdk/imagepicker/

  2. Expo Camera: https://docs.expo.dev/versions/latest/sdk/camera/

  3. Expo Image Picker Tutorial: https://docs.expo.dev/tutorial/image-picker/

  4. Expo Permissions Guide: https://docs.expo.dev/guides/permissions/

  5. React Native Image Component: https://reactnative.dev/docs/image

  6. React Native Networking: https://reactnative.dev/docs/network

←
Post 26 of 39
→