| name | mobile-camera-integration |
|---|---|
| description | Add camera functionality to an Expo app using expo-camera. Covers permissions, photo capture, barcode scanning, video recording, and saving to the camera roll. Use when the user wants to take photos, scan barcodes, or record video. |
| standards-version | 1.6.3 |
Use this skill when the user:
- Wants to add camera functionality to their app
- Needs to capture photos or record video
- Wants barcode or QR code scanning
- Asks about camera permissions on iOS or Android
- Mentions "camera", "photo", "barcode", "QR code", "scan", or "video recording"
- Use case: photo capture, barcode scanning, video recording, or a combination
- Save behavior: save to camera roll, upload to server, or display preview only
- Barcode types (optional): QR, EAN-13, Code 128, etc.
-
Install dependencies. expo-camera and expo-media-library (for saving photos):
npx expo install expo-camera expo-media-library
These are native modules and require a development build. They will not work in Expo Go.
npx expo prebuild npx expo run:ios # or run:android -
Request camera permission. Use the
useCameraPermissionshook:import { CameraView, useCameraPermissions } from "expo-camera"; import { View, Text, Button, StyleSheet } from "react-native"; export default function CameraScreen() { const [permission, requestPermission] = useCameraPermissions(); if (!permission) { return <View />; } if (!permission.granted) { return ( <View style={styles.container}> <Text>Camera access is required to take photos.</Text> <Button title="Grant Permission" onPress={requestPermission} /> </View> ); } return ( <View style={styles.container}> <CameraView style={styles.camera} facing="back" /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1 }, camera: { flex: 1 }, });
-
Capture a photo. Use a ref to call
takePictureAsync:import { useRef, useState } from "react"; import { CameraView, useCameraPermissions } from "expo-camera"; import { View, Image, TouchableOpacity, StyleSheet } from "react-native"; import { Ionicons } from "@expo/vector-icons"; export default function CameraScreen() { const cameraRef = useRef<CameraView>(null); const [photo, setPhoto] = useState<string | null>(null); const [permission, requestPermission] = useCameraPermissions(); const [facing, setFacing] = useState<"front" | "back">("back"); async function takePhoto() { if (!cameraRef.current) return; const result = await cameraRef.current.takePictureAsync({ quality: 0.8, base64: false, }); if (result) setPhoto(result.uri); } function toggleFacing() { setFacing((f) => (f === "back" ? "front" : "back")); } if (photo) { return ( <View style={styles.container}> <Image source={{ uri: photo }} style={styles.camera} /> <TouchableOpacity style={styles.button} onPress={() => setPhoto(null)} > <Ionicons name="close" size={32} color="white" /> </TouchableOpacity> </View> ); } return ( <View style={styles.container}> <CameraView ref={cameraRef} style={styles.camera} facing={facing}> <View style={styles.controls}> <TouchableOpacity onPress={toggleFacing}> <Ionicons name="camera-reverse" size={32} color="white" /> </TouchableOpacity> <TouchableOpacity onPress={takePhoto}> <Ionicons name="radio-button-on" size={72} color="white" /> </TouchableOpacity> </View> </CameraView> </View> ); } const styles = StyleSheet.create({ container: { flex: 1 }, camera: { flex: 1 }, controls: { flex: 1, flexDirection: "row", justifyContent: "space-around", alignItems: "flex-end", paddingBottom: 40, }, button: { position: "absolute", top: 60, right: 20, }, });
-
Save to camera roll. Use expo-media-library:
import * as MediaLibrary from "expo-media-library"; async function savePhoto(uri: string) { const { status } = await MediaLibrary.requestPermissionsAsync(); if (status !== "granted") { alert("Media library permission is required to save photos."); return; } await MediaLibrary.saveToLibraryAsync(uri); }
-
Barcode scanning. Enable
barcodeScannerSettingsonCameraView:import { CameraView } from "expo-camera"; import type { BarcodeScanningResult } from "expo-camera"; function handleBarcodeScanned(result: BarcodeScanningResult) { console.log(`Type: ${result.type}, Data: ${result.data}`); } <CameraView style={{ flex: 1 }} facing="back" barcodeScannerSettings={{ barcodeTypes: ["qr", "ean13", "code128"], }} onBarcodeScanned={handleBarcodeScanned} />
Supported barcode types:
qr,aztec,ean13,ean8,pdf417,upc_e,datamatrix,code39,code93,code128,itf14,codabar. -
Add permission rationale to app.json. Required for iOS App Store submission:
{ "expo": { "plugins": [ [ "expo-camera", { "cameraPermission": "This app uses the camera to take photos." } ], [ "expo-media-library", { "photosPermission": "This app saves photos to your camera roll.", "savePhotosPermission": "This app saves photos to your camera roll." } ] ] } }Use
mobile_addPermissionto automate this step.
User: "I want to build a QR code scanner that opens URLs."
Agent:
- Installs expo-camera with
mobile_installDependency - Adds camera permission with
mobile_addPermission - Creates a scanner screen with
mobile_generateScreen - Implements
CameraViewwithbarcodeScannerSettingsfor QR codes - Adds
Linking.openURLon scan result - Handles permission denied state with a settings link
| Step | MCP Tool | Description |
|---|---|---|
| Install expo-camera | mobile_installDependency |
Run npx expo install expo-camera expo-media-library |
| Add permission config | mobile_addPermission |
Add camera permission rationale to app.json |
| Create camera screen | mobile_generateScreen |
Scaffold a screen file for the camera view |
| Check build | mobile_checkBuildHealth |
Verify the project builds after adding native modules |
- Using expo-camera in Expo Go - expo-camera requires a development build. It will crash in Expo Go. Run
npx expo prebuildfirst. - Forgetting permission rationale - iOS rejects apps without usage description strings. Always add the
cameraPermissionplugin config toapp.json. - Not handling permission denied - Users can deny or permanently block camera access. Check
permission.canAskAgainand link to system settings if blocked. - Importing from wrong package - Use
CameraViewfromexpo-camera, not the deprecatedCameracomponent. The API changed in expo-camera v15. - Barcode scanner firing repeatedly -
onBarcodeScannedfires on every frame. Debounce it or set a scanned flag to prevent duplicate processing. - Missing media library permission - Saving to the camera roll requires a separate
expo-media-librarypermission. Request it before callingsaveToLibraryAsync.
- Mobile Permissions - detailed permission handling patterns
- Mobile AI Features - send captured photos to AI vision APIs