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.
