or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

barcode-scanning.mdcamera-component.mdindex.mdmedia-capture.mdpermissions.md
tile.json

permissions.mddocs/

Permissions Management

Expo Camera provides comprehensive permission management for camera and microphone access through React hooks and direct async functions. This ensures proper user consent and handles platform-specific permission requirements.

Capabilities

Camera Permissions Hook

React hook for managing camera permissions with automatic status monitoring.

/**
 * Hook for camera permission management
 * @param options Optional configuration for permission requests
 * @returns Tuple containing [status, requestPermission, getPermission]
 */
const useCameraPermissions: (
  options?: PermissionHookOptions<object>
) => [
  PermissionResponse | null,
  () => Promise<PermissionResponse>,
  () => Promise<PermissionResponse>
];

Microphone Permissions Hook

React hook for managing microphone permissions (required for video recording with audio).

/**
 * Hook for microphone permission management
 * @param options Optional configuration for permission requests
 * @returns Tuple containing [status, requestPermission, getPermission]
 */
const useMicrophonePermissions: (
  options?: PermissionHookOptions<object>
) => [
  PermissionResponse | null,
  () => Promise<PermissionResponse>,
  () => Promise<PermissionResponse>
];

Direct Permission Functions

Async functions for direct permission management without hooks.

/**
 * Check current camera permission status
 * @returns Promise resolving to current permission response
 */
function getCameraPermissionsAsync(): Promise<PermissionResponse>;

/**
 * Request camera permissions from the user
 * On iOS requires NSCameraUsageDescription in Info.plist
 * @returns Promise resolving to permission response after user interaction
 */
function requestCameraPermissionsAsync(): Promise<PermissionResponse>;

/**
 * Check current microphone permission status
 * @returns Promise resolving to current permission response
 */
function getMicrophonePermissionsAsync(): Promise<PermissionResponse>;

/**
 * Request microphone permissions from the user
 * On iOS requires NSMicrophoneUsageDescription in Info.plist
 * @returns Promise resolving to permission response after user interaction
 */
function requestMicrophonePermissionsAsync(): Promise<PermissionResponse>;

Legacy Camera Object

For backward compatibility, all permission functions are also available through the legacy Camera object.

/**
 * Legacy Camera object containing permission methods and scanFromURLAsync
 * @deprecated Use named exports instead
 */
const Camera = {
  /**
   * Check current camera permission status
   * @returns Promise resolving to current permission response
   */
  getCameraPermissionsAsync(): Promise<PermissionResponse>;
  
  /**
   * Request camera permissions from the user
   * @returns Promise resolving to permission response after user interaction
   */
  requestCameraPermissionsAsync(): Promise<PermissionResponse>;
  
  /**
   * Check current microphone permission status
   * @returns Promise resolving to current permission response
   */
  getMicrophonePermissionsAsync(): Promise<PermissionResponse>;
  
  /**
   * Request microphone permissions from the user
   * @returns Promise resolving to permission response after user interaction
   */
  requestMicrophonePermissionsAsync(): Promise<PermissionResponse>;
  
  /**
   * Scan barcodes from image URL
   * @param url URL of the image to scan
   * @param barcodeTypes Array of barcode types (defaults to ['qr'])
   * @returns Promise resolving to array of detected barcodes
   */
  scanFromURLAsync(url: string, barcodeTypes?: BarcodeType[]): Promise<BarcodeScanningResult[]>;
};

Permission Types

Core types for permission management.

interface PermissionResponse {
  /**
   * Current permission status
   */
  status: PermissionStatus;
  
  /**
   * When the permission expires (if applicable)
   */
  expires: PermissionExpiration;
  
  /**
   * Whether permission can be asked again
   */
  canAskAgain: boolean;
  
  /**
   * Whether permission has been granted
   */
  granted: boolean;
}

type PermissionStatus = 
  | 'undetermined'
  | 'granted' 
  | 'denied';

type PermissionExpiration = 'never' | number;

interface PermissionHookOptions<T> {
  /**
   * Request permission immediately when hook is called
   */
  request?: boolean;
  
  /**
   * Additional data to pass with permission request
   */
  data?: T;
}

Usage Examples

Basic Hook Usage

import React, { useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { CameraView, useCameraPermissions } from 'expo-camera';

export function CameraWithPermissions() {
  const [permission, requestPermission] = useCameraPermissions();

  if (!permission) {
    // Camera permissions are still loading
    return (
      <View style={styles.container}>
        <Text>Loading permissions...</Text>
      </View>
    );
  }

  if (!permission.granted) {
    // Camera permissions are not granted yet
    return (
      <View style={styles.container}>
        <Text style={styles.message}>
          We need your permission to show the camera
        </Text>
        <TouchableOpacity style={styles.button} onPress={requestPermission}>
          <Text style={styles.buttonText}>Grant Permission</Text>
        </TouchableOpacity>
        {!permission.canAskAgain && (
          <Text style={styles.warning}>
            Please enable camera permissions in device settings
          </Text>
        )}
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <CameraView style={styles.camera} facing="back" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
  },
  message: {
    textAlign: 'center',
    paddingBottom: 10,
  },
  camera: {
    flex: 1,
  },
  button: {
    padding: 10,
    backgroundColor: '#007AFF',
    margin: 20,
    borderRadius: 5,
  },
  buttonText: {
    color: 'white',
    textAlign: 'center',
  },
  warning: {
    textAlign: 'center',
    color: 'red',
    fontSize: 12,
  },
});

Video Recording with Microphone Permissions

import React, { useState, useRef } from 'react';
import { View, TouchableOpacity, Text, Alert, StyleSheet } from 'react-native';
import { 
  CameraView, 
  useCameraPermissions, 
  useMicrophonePermissions,
  CameraRecordingOptions 
} from 'expo-camera';

export function VideoRecordingCamera() {
  const [isRecording, setIsRecording] = useState(false);
  const [cameraPermission, requestCameraPermission] = useCameraPermissions();
  const [microphonePermission, requestMicrophonePermission] = useMicrophonePermissions();
  const cameraRef = useRef<CameraView>(null);

  const checkPermissions = async () => {
    if (!cameraPermission?.granted) {
      const result = await requestCameraPermission();
      if (!result.granted) {
        Alert.alert('Camera permission is required');
        return false;
      }
    }

    if (!microphonePermission?.granted) {
      const result = await requestMicrophonePermission();
      if (!result.granted) {
        Alert.alert('Microphone permission is required for video with audio');
        return false;
      }
    }

    return true;
  };

  const startRecording = async () => {
    if (!cameraRef.current || !(await checkPermissions())) return;

    try {
      setIsRecording(true);
      const recordingOptions: CameraRecordingOptions = {
        maxDuration: 60, // 60 seconds max
        maxFileSize: 100 * 1024 * 1024, // 100MB max
      };
      
      const video = await cameraRef.current.record(recordingOptions);
      console.log('Video recorded:', video.uri);
    } catch (error) {
      Alert.alert('Recording failed', error.message);
    } finally {
      setIsRecording(false);
    }
  };

  const stopRecording = async () => {
    if (cameraRef.current && isRecording) {
      await cameraRef.current.stopRecording();
      setIsRecording(false);
    }
  };

  if (!cameraPermission || !microphonePermission) {
    return (
      <View style={styles.container}>
        <Text>Loading permissions...</Text>
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <CameraView
        ref={cameraRef}
        style={styles.camera}
        facing="back"
        mode="video"
      />
      <View style={styles.controls}>
        <TouchableOpacity
          style={[styles.button, isRecording && styles.recordingButton]}
          onPress={isRecording ? stopRecording : startRecording}
        >
          <Text style={styles.buttonText}>
            {isRecording ? 'Stop Recording' : 'Start Recording'}
          </Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  camera: { flex: 1 },
  controls: {
    position: 'absolute',
    bottom: 50,
    left: 0,
    right: 0,
    alignItems: 'center',
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 15,
    borderRadius: 25,
  },
  recordingButton: {
    backgroundColor: '#FF3B30',
  },
  buttonText: {
    color: 'white',
    fontWeight: 'bold',
  },
});

Direct Permission Functions

import React, { useEffect, useState } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { 
  getCameraPermissionsAsync, 
  requestCameraPermissionsAsync,
  PermissionResponse 
} from 'expo-camera';

export function DirectPermissionExample() {
  const [cameraPermission, setCameraPermission] = useState<PermissionResponse | null>(null);

  useEffect(() => {
    checkCameraPermissions();
  }, []);

  const checkCameraPermissions = async () => {
    try {
      const permission = await getCameraPermissionsAsync();
      setCameraPermission(permission);
    } catch (error) {
      console.error('Failed to check camera permissions:', error);
    }
  };

  const requestCameraPermissions = async () => {
    try {
      const permission = await requestCameraPermissionsAsync();
      setCameraPermission(permission);
      
      if (permission.granted) {
        console.log('Camera permission granted');
      } else if (!permission.canAskAgain) {
        console.log('User denied permission permanently');
      } else {
        console.log('User denied permission');
      }
    } catch (error) {
      console.error('Failed to request camera permissions:', error);
    }
  };

  return (
    <View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
      <Text style={{ marginBottom: 20 }}>
        Camera Permission Status: {cameraPermission?.status || 'Unknown'}
      </Text>
      <Text style={{ marginBottom: 20 }}>
        Granted: {cameraPermission?.granted ? 'Yes' : 'No'}
      </Text>
      <Text style={{ marginBottom: 20 }}>
        Can Ask Again: {cameraPermission?.canAskAgain ? 'Yes' : 'No'}
      </Text>
      
      {!cameraPermission?.granted && (
        <TouchableOpacity
          style={{
            backgroundColor: '#007AFF',
            padding: 15,
            borderRadius: 5,
            alignItems: 'center',
          }}
          onPress={requestCameraPermissions}
        >
          <Text style={{ color: 'white' }}>Request Camera Permission</Text>
        </TouchableOpacity>
      )}
    </View>
  );
}

Permission Status Monitoring

import React, { useState, useEffect } from 'react';
import { View, Text } from 'react-native';
import { 
  useCameraPermissions, 
  useMicrophonePermissions,
  PermissionResponse 
} from 'expo-camera';

export function PermissionMonitor() {
  const [cameraPermission, requestCameraPermission, getCameraPermission] = useCameraPermissions();
  const [microphonePermission, requestMicrophonePermission, getMicrophonePermission] = useMicrophonePermissions();

  // Monitor permission changes
  useEffect(() => {
    const intervalId = setInterval(async () => {
      // Manually refresh permissions if needed
      await getCameraPermission();
      await getMicrophonePermission();
    }, 5000); // Check every 5 seconds

    return () => clearInterval(intervalId);
  }, [getCameraPermission, getMicrophonePermission]);

  const renderPermissionStatus = (permission: PermissionResponse | null, name: string) => (
    <View style={{ marginBottom: 20 }}>
      <Text style={{ fontWeight: 'bold' }}>{name} Permission:</Text>
      <Text>Status: {permission?.status || 'Loading...'}</Text>
      <Text>Granted: {permission?.granted ? 'Yes' : 'No'}</Text>
      <Text>Can Ask Again: {permission?.canAskAgain ? 'Yes' : 'No'}</Text>
      <Text>Expires: {permission?.expires === 'never' ? 'Never' : permission?.expires}</Text>
    </View>
  );

  return (
    <View style={{ flex: 1, padding: 20 }}>
      <Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 20 }}>
        Permission Status Monitor
      </Text>
      
      {renderPermissionStatus(cameraPermission, 'Camera')}
      {renderPermissionStatus(microphonePermission, 'Microphone')}
    </View>
  );
}

Error Handling

import React from 'react';
import { View, Text, TouchableOpacity, Alert } from 'react-native';
import { useCameraPermissions } from 'expo-camera';

export function PermissionErrorHandling() {
  const [permission, requestPermission] = useCameraPermissions();

  const handlePermissionRequest = async () => {
    try {
      const result = await requestPermission();
      
      if (result.granted) {
        Alert.alert('Success', 'Camera permission granted');
      } else if (!result.canAskAgain) {
        Alert.alert(
          'Permission Denied',
          'Camera permission was permanently denied. Please enable it in device settings.',
          [
            { text: 'Cancel', style: 'cancel' },
            { text: 'Open Settings', onPress: () => {
              // Open device settings - platform specific implementation needed
              console.log('Would open device settings');
            }}
          ]
        );
      } else {
        Alert.alert('Permission Required', 'Camera access is needed to take photos');
      }
    } catch (error) {
      Alert.alert('Error', `Failed to request permission: ${error.message}`);
    }
  };

  return (
    <View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
      {permission?.granted ? (
        <Text style={{ textAlign: 'center', fontSize: 18 }}>
          Camera permission is granted! 📸
        </Text>
      ) : (
        <TouchableOpacity
          style={{
            backgroundColor: '#007AFF',
            padding: 15,
            borderRadius: 5,
            alignItems: 'center',
          }}
          onPress={handlePermissionRequest}
        >
          <Text style={{ color: 'white' }}>Request Camera Permission</Text>
        </TouchableOpacity>
      )}
    </View>
  );
}