iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…