React Native Camera and Media

Mobile apps regularly let users take photos, pick images from their gallery, or record video. Expo provides well-maintained libraries that handle camera access, permissions, and media selection without writing any native code.

The Two Main Packages

Package                    Use it for
────────────────────────────────────────────────────────────
expo-image-picker          Pick photos/videos from gallery OR take a photo
expo-camera                Build a full custom camera view in your app
expo-av                    Play audio and video files
expo-media-library         Save media to the device gallery, browse saved photos

Image Picker — The Fastest Path to Photos

expo-image-picker covers the most common case: let the user pick a photo from their gallery or take a new one, then use it in your app.

npx expo install expo-image-picker

Picking from the Gallery

import * as ImagePicker from 'expo-image-picker';
import { useState } from 'react';
import { View, Image, TouchableOpacity, Text } from 'react-native';

export default function AvatarPicker() {
  const [imageUri, setImageUri] = useState(null);

  async function pickImage() {
    // Ask permission first
    const permission = await ImagePicker.requestMediaLibraryPermissionsAsync();
    if (!permission.granted) {
      alert('Gallery permission is required.');
      return;
    }

    const result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      allowsEditing: true,     // show crop tool
      aspect: [1, 1],          // square crop
      quality: 0.8,            // 80% JPEG quality
    });

    if (!result.canceled) {
      setImageUri(result.assets[0].uri);
    }
  }

  return (
    <View style={{ alignItems: 'center', padding: 20 }}>
      {imageUri ? (
        <Image source={{ uri: imageUri }} style={{ width: 120, height: 120, borderRadius: 60 }} />
      ) : (
        <View style={{ width: 120, height: 120, borderRadius: 60, backgroundColor: '#ddd', justifyContent: 'center', alignItems: 'center' }}>
          <Text>No Photo</Text>
        </View>
      )}
      <TouchableOpacity onPress={pickImage} style={{ marginTop: 16, padding: 12, backgroundColor: '#007AFF', borderRadius: 8 }}>
        <Text style={{ color: 'white' }}>Choose Photo</Text>
      </TouchableOpacity>
    </View>
  );
}

Taking a Photo with the Camera

async function takePhoto() {
  const permission = await ImagePicker.requestCameraPermissionsAsync();
  if (!permission.granted) {
    alert('Camera permission is required.');
    return;
  }

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

  if (!result.canceled) {
    setImageUri(result.assets[0].uri);
  }
}
Flow diagram:
User taps "Take Photo"
      │
      ▼
requestCameraPermissionsAsync()
      │
      ├── granted → launchCameraAsync()
      │                  │
      │                  ├── user takes photo → result.assets[0].uri
      │                  │
      │                  └── user cancels → result.canceled = true
      │
      └── denied → show alert

Uploading an Image to a Server

After picking an image, most apps upload it. Use FormData to send the file in a multipart request.

async function uploadImage(uri) {
  const filename = uri.split('/').pop();
  const type = 'image/jpeg';

  const formData = new FormData();
  formData.append('photo', { uri, name: filename, type });

  const response = await fetch('https://api.example.com/upload', {
    method: 'POST',
    body: formData,
    headers: { 'Content-Type': 'multipart/form-data' },
  });

  const result = await response.json();
  return result.url;   // the URL of the uploaded image on the server
}

expo-camera — Building a Custom Camera Screen

Use expo-camera when you need a full in-app camera view — for example, a QR code scanner or a custom photo booth screen.

npx expo install expo-camera
import { CameraView, useCameraPermissions } from 'expo-camera';
import { useState } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';

export default function CameraScreen() {
  const [permission, requestPermission] = useCameraPermissions();
  const [cameraRef, setCameraRef] = useState(null);

  if (!permission) return <View />;

  if (!permission.granted) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <Text>Camera access is needed.</Text>
        <TouchableOpacity onPress={requestPermission}>
          <Text style={{ color: '#007AFF', marginTop: 12 }}>Grant Permission</Text>
        </TouchableOpacity>
      </View>
    );
  }

  async function snap() {
    if (cameraRef) {
      const photo = await cameraRef.takePictureAsync({ quality: 0.8 });
      console.log('Photo URI:', photo.uri);
    }
  }

  return (
    <View style={{ flex: 1 }}>
      <CameraView style={{ flex: 1 }} ref={setCameraRef} />
      <TouchableOpacity
        onPress={snap}
        style={{ position: 'absolute', bottom: 40, alignSelf: 'center',
                 width: 70, height: 70, borderRadius: 35,
                 backgroundColor: 'white', borderWidth: 4, borderColor: '#007AFF' }}
      />
    </View>
  );
}
Camera screen layout:
┌─────────────────────────────┐
│                             │
│      Live Camera View       │
│                             │
│                             │
│         ●                   │  ← shutter button (white circle)
└─────────────────────────────┘

Playing Video with expo-av

npx expo install expo-av

import { Video } from 'expo-av';

<Video
  source={{ uri: 'https://example.com/video.mp4' }}
  style={{ width: '100%', height: 220 }}
  useNativeControls      // shows play/pause/seek controls
  resizeMode="contain"
  isLooping
/>

Permissions — What to Show Users

iOS and Android require explicit permission before accessing the camera or photo library. Always ask with a clear reason. If denied, show a helpful message and a button that opens the device settings.

import { Linking } from 'react-native';

function PermissionDeniedMessage() {
  return (
    <View style={{ alignItems: 'center', padding: 20 }}>
      <Text>Camera access was denied.</Text>
      <TouchableOpacity onPress={() => Linking.openSettings()}>
        <Text style={{ color: '#007AFF', marginTop: 8 }}>Open Settings</Text>
      </TouchableOpacity>
    </View>
  );
}

Summary

Use expo-image-picker for letting users pick gallery images or take photos — it is the fastest and most common solution. Use expo-camera when you need a live camera view inside your app. Always request permissions before accessing any media feature. Upload images using FormData with a multipart POST request. Use expo-av for video playback.

Leave a Comment