Camera
Native camera + photo picker on React Native: Expo’s expo-camera (managed, modern), expo-image-picker (gallery), and react-native-vision-camera (bare RN, more features). Pick based on workflow; both wrap the same underlying APIs with permission handling and image quality controls.
Permissions, capture, library, upload
EXAMPLE
// 1) Setup — Expo
// npx expo install expo-camera expo-image-picker expo-media-library expo-file-system
import { CameraView, useCameraPermissions } from 'expo-camera';
import * as ImagePicker from 'expo-image-picker';
import * as FileSystem from 'expo-file-system';
import { useState, useRef } from 'react';
import { View, Button, Image, Text, TouchableOpacity } from 'react-native';
// 2) Capture flow — expo-camera
export function PhotoScreen() {
const [permission, requestPermission] = useCameraPermissions();
const [facing, setFacing] = useState<'back' | 'front'>('back');
const [photo, setPhoto] = useState<string | null>(null);
const cameraRef = useRef<CameraView>(null);
if (!permission) return <Text>Requesting permission…</Text>;
if (!permission.granted) return (
<View>
<Text>Camera access required</Text>
<Button title="Grant permission" onPress={requestPermission} />
</View>
);
async function take() {
const result = await cameraRef.current?.takePictureAsync({ quality: 0.8, base64: false });
if (result?.uri) setPhoto(result.uri);
}
return (
<View style={{ flex: 1 }}>
<CameraView ref={cameraRef} style={{ flex: 1 }} facing={facing} />
<View style={{ flexDirection: 'row', padding: 16 }}>
<Button title="Take" onPress={take} />
<Button title="Flip" onPress={() => setFacing((f) => f === 'back' ? 'front' : 'back')} />
</View>
{photo && <Image source={{ uri: photo }} style={{ width: 100, height: 100 }} />}
</View>
);
}
// 3) Pick from gallery — expo-image-picker
async function pickFromLibrary() {
const perm = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (!perm.granted) return;
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 0.8,
});
if (!result.canceled) {
const photo = result.assets[0];
console.log(photo.uri, photo.width, photo.height);
}
}
// 4) Take + save to media library
import * as MediaLibrary from 'expo-media-library';
async function takeAndSave() {
const result = await cameraRef.current?.takePictureAsync({ quality: 0.8 });
if (!result) return;
await MediaLibrary.requestPermissionsAsync();
await MediaLibrary.saveToLibraryAsync(result.uri);
}
// 5) Upload to a backend
async function upload(uri: string) {
const filename = uri.split('/').pop() ?? 'photo.jpg';
const form = new FormData();
form.append('file', { uri, name: filename, type: 'image/jpeg' } as any);
const r = await fetch('https://api.example.com/upload', {
method: 'POST',
body: form,
headers: { Authorization: `Bearer ${token}` },
});
return r.json();
}
// 6) Compress / resize before upload (saves bandwidth)
import * as ImageManipulator from 'expo-image-manipulator';
async function shrink(uri: string) {
const result = await ImageManipulator.manipulateAsync(
uri,
[{ resize: { width: 1280 } }],
{ compress: 0.7, format: ImageManipulator.SaveFormat.JPEG },
);
return result.uri;
}
// 7) Permissions — Info.plist (iOS)
// <key>NSCameraUsageDescription</key>
// <string>Take photos to attach to posts</string>
// <key>NSPhotoLibraryUsageDescription</key>
// <string>Pick photos from your library</string>
// <key>NSPhotoLibraryAddUsageDescription</key>
// <string>Save photos back to your library</string>
//
// Android Manifest
// <uses-permission android:name="android.permission.CAMERA" />
// <uses-feature android:name="android.hardware.camera" android:required="false" />
//
// Expo handles these via app.json:
{
"expo": {
"plugins": [
["expo-camera", { "cameraPermission": "Take photos", "microphonePermission": "Record audio" }],
["expo-image-picker", { "photosPermission": "Choose photos", "cameraPermission": "Take photos" }]
]
}
}
// 8) Bare RN — react-native-vision-camera
// More performant, supports video, code scanning, ML kit integration, custom frame processors.
// More setup; pick when you need:
// • 4K / 60fps video
// • QR / barcode scanning
// • Real-time frame processing (ML, AR)
// • Hardware-level controls (focus, exposure, ISO, zoom)
import { Camera, useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
function VisionExample() {
const { hasPermission, requestPermission } = useCameraPermission();
const device = useCameraDevice('back');
if (!hasPermission) return <Button title="Grant" onPress={requestPermission} />;
if (!device) return <Text>No camera</Text>;
return <Camera style={{ flex: 1 }} device={device} isActive={true} />;
}
// 9) Recording video
<CameraView ref={cameraRef} mode="video" videoQuality="720p" />
await cameraRef.current?.recordAsync({ maxDuration: 30 });
await cameraRef.current?.stopRecording();
// 10) Capture metadata
const result = await cameraRef.current?.takePictureAsync({
exif: true, // include EXIF (location, timestamp)
quality: 0.9,
});
console.log(result?.exif);
// Strip EXIF before upload if location privacy matters:
await ImageManipulator.manipulateAsync(uri, [], { compress: 0.9, format: ImageManipulator.SaveFormat.JPEG });
// Manipulator generally strips EXIF when re-encoding.
// 11) Multi-shot / burst
// Loop takePictureAsync with short intervals; or move to vision-camera for true burst mode.
// 12) Common UX patterns
// • Show preview after capture; let user retake or confirm
// • Show progress bar during upload; allow cancel
// • Cache locally before upload; retry on failure
// • Show file size / dimensions before upload to set expectations
// • Limit selection to N photos in picker
// 13) Permission state machine
// status: undetermined → request → granted | denied
// On 'denied': open settings explanation, never auto-prompt again
// Use Linking.openSettings() to deep-link to app settings
// 14) Common bugs
// • Forgot usage descriptions → silent crash on iOS or denial on Android
// • Permissions granted at install but revoked later → re-request on use
// • Huge images crashing on Android low memory → resize before display
// • Upload as 'image/png' for JPEG file → server may reject
// • FormData on RN — types differ from web; use `as any` or proper types
// • Wrong camera ratio / preview cropping — set ratio in CameraView props
// • Saving without MediaLibrary permission → fails silently on some Androids
// • Vision Camera mounted multiple times — only one Camera at a time per device
// • Capturing during background → returns null; check app state
Why it matters
Expo Camera + Image Picker covers most camera use cases with permission helpers, image quality controls, and FormData uploads. Resize + compress before upload (expo-image-manipulator), strip EXIF for privacy, and reach for react-native-vision-camera when you need 60fps video, barcode scanning, or real-time frame processing in a bare RN app.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { CameraView, useCameraPermissions } from 'expo-camera';
const [permission, request] = useCameraPermissions();
if (!permission?.granted) return <Button onPress={request} title="Allow camera" />;
return <CameraView style={{ flex: 1 }} />;
Try it Yourself »
Discussion
Loading…