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

Camera

Capacitor’s Camera plugin gives you native camera + photo-picker access from one API across iOS, Android, and the web. Configure permissions, handle the resulting blob, and you have a working photo upload in under a hundred lines.

plugin, permissions, gallery, upload

EXAMPLE
// 1) Install
// npm install @capacitor/camera @capacitor/filesystem
// npx cap sync

import { Component, signal } from '@angular/core';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';

// 2) Basic capture
@Component({
    selector: 'app-photo',
    template: `
        <ion-button (click)="takePhoto()">Take photo</ion-button>
        <img *ngIf="image()" [src]="image()" alt="photo" style="max-width:100%">
    `,
})
export class PhotoPage {
    image = signal<string | null>(null);

    async takePhoto() {
        const photo: Photo = await Camera.getPhoto({
            quality:           90,
            allowEditing:      false,
            resultType:        CameraResultType.Uri,         // file:// URI
            source:             CameraSource.Camera,           // CAMERA, PHOTOS, or PROMPT
            saveToGallery:     false,
            correctOrientation: true,
            width:              1080,
            height:             1080,
            promptLabelHeader:  'Photo',
            promptLabelPhoto:   'From gallery',
            promptLabelPicture: 'Take new',
        });
        this.image.set(photo.webPath ?? null);                // safe to use in <img>
    }
}

// 3) Permissions (iOS info.plist)
// <key>NSCameraUsageDescription</key>
// <string>Take photos to attach to your posts.</string>
// <key>NSPhotoLibraryUsageDescription</key>
// <string>Choose photos from your library.</string>
// <key>NSPhotoLibraryAddUsageDescription</key>
// <string>Save shots back to your photo library.</string>

// Android — AndroidManifest.xml
// <uses-permission android:name="android.permission.CAMERA"/>
// <uses-feature android:name="android.hardware.camera" android:required="false"/>
// (Storage permissions handled by Capacitor + scoped storage on Android 11+)

// 4) Request permissions explicitly when you need to
const status = await Camera.checkPermissions();
if (status.camera !== 'granted' || status.photos !== 'granted') {
    const after = await Camera.requestPermissions({ permissions: ['camera', 'photos'] });
    if (after.camera !== 'granted') {
        // Direct user to settings
    }
}

// 5) Result types — pick the right one for your flow
// • CameraResultType.Uri        — returns webPath (file://) — fastest, no memory copy
// • CameraResultType.Base64      — returns base64String — easy to embed, large memory hit
// • CameraResultType.DataUrl     — returns a data: URI ready for <img src> — also memory-heavy
//
// Use Uri for any photo bigger than a thumbnail; convert as needed.

// 6) Read the photo bytes from Filesystem
import { Filesystem, Directory } from '@capacitor/filesystem';

async function readAsBlob(webPath: string): Promise<Blob> {
    const response = await fetch(webPath);           // webPath is a fetch-safe URL
    return await response.blob();
}

async function readAsBase64(uri: string): Promise<string> {
    const r = await Filesystem.readFile({ path: uri });
    return r.data as string;
}

// 7) Save to app sandbox (persistent across launches)
async function persist(uri: string, filename: string) {
    const blob = await readAsBlob(uri);
    const reader = new FileReader();
    return new Promise<void>((resolve, reject) => {
        reader.onload = async () => {
            const base64 = (reader.result as string).split(',')[1];
            await Filesystem.writeFile({
                path:      `photos/${filename}`,
                data:      base64,
                directory: Directory.Data,
                recursive: true,
            });
            resolve();
        };
        reader.onerror = reject;
        reader.readAsDataURL(blob);
    });
}

// 8) Upload to your API
async function upload(uri: string) {
    const blob = await readAsBlob(uri);
    const form = new FormData();
    form.append('file', blob, 'photo.jpg');
    form.append('purpose', 'avatar');
    const res = await fetch('https://api.example.com/upload', {
        method: 'POST',
        body:   form,
    });
    return await res.json();
}

// On native, use the Capacitor HTTP plugin to bypass CORS and get reliable
// background uploads — see the @capacitor/http docs.

// 9) Choose the source — camera vs gallery vs prompt
await Camera.getPhoto({ source: CameraSource.Camera });    // straight to camera (with permission)
await Camera.getPhoto({ source: CameraSource.Photos });    // skip camera, use gallery picker
await Camera.getPhoto({ source: CameraSource.Prompt });    // user picks ('Take new' / 'Choose from library')

// 10) Multiple photos — Capacitor 5+ has Camera.pickImages
const { photos } = await Camera.pickImages({ quality: 80, limit: 10 });
photos.forEach(p => console.log(p.webPath));

// 11) Web fallback
// On the web, getPhoto opens the system file picker (input type="file" capture="environment")
// Photos return as webPath (blob: URL). FileSystem APIs are limited; use IndexedDB or remote storage.

// 12) Quality + size considerations
// • quality:  1-100 (jpeg)
// • width / height — Capacitor downscales server-side; cheaper than uploading the full image
// • Ask for the smallest dimensions your UI actually needs
// • For profile photos, 512x512 is usually enough; for documents, 1500-2000px on the long side

// 13) Handling errors
try {
    const p = await Camera.getPhoto({ /* … */ });
} catch (e: any) {
    if (e?.message?.includes('User cancelled')) {
        return;   // user just backed out — quiet failure
    }
    showError('Could not open camera. Check permissions.');
}

// 14) UX patterns
// • Show a placeholder + 'Add photo' button BEFORE permission is requested
// • Explain why you need the camera/library access (a sentence above the trigger)
// • Offer 'Take new' AND 'Choose from library' (use CameraSource.Prompt)
// • Show progress for uploads of large photos (network can be slow on mobile)
// • Provide a 'Retake' option after preview

// 15) Background uploads (Android) — Capacitor doesn't ship a background uploader.
// Use a community plugin (@capacitor-community/background-task) or build a foreground service.
// Alternative: resilient chunked uploads from the foreground with progress + retry.

// 16) iOS Live Photo / RAW / HEIC
// • getPhoto returns JPEG by default; HEIC available on supported devices via the format option
// • Live photos: only the still frame is returned; the motion video requires the iOS Photos framework

// 17) Privacy — handling the resulting image responsibly
// • Don't ship EXIF GPS to the server unless you need it; strip with an image library
// • Don't write photos to a publicly readable Directory.External path on Android
// • Delete files when the user removes them; use Filesystem.deleteFile

// 18) Common bugs
// • Missing usage description → app crashes on first prompt (iOS) or silent denial (Android)
// • Using src="file://…" on the web — only webPath / DataUrl are valid for <img> there
// • Forgetting npx cap sync after install — native side doesn't know about the new plugin
// • Capturing huge images (quality 100, no width) → memory pressure, OOM on mid-range phones
// • Granting 'photos' permission but not 'camera' — getPhoto with CameraSource.Camera silently fails
// • Uploading the base64 string instead of the blob → 33% bigger payload
// • Not handling 'user cancelled' → confusing error toast

Why it matters

Reach for CameraResultType.Uri over Base64 for performance, set width/quality server-side to avoid memory pressure on cheap devices, and write the required usage strings up front — NSCameraUsageDescription and friends turn silent crashes into a friendly permission prompt. Use CameraSource.Prompt when you want users to choose between “take new” and “pick from library”.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import { Camera, CameraResultType } from '@capacitor/camera';
const photo = await Camera.getPhoto({
    quality: 80,
    resultType: CameraResultType.Uri,
});
console.log(photo.webPath);
Try it Yourself »

Discussion

Loading…