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

barcode-scanning.mddocs/

Barcode Scanning

Expo Camera provides comprehensive barcode and QR code scanning capabilities supporting multiple barcode formats with real-time detection during camera preview and static image scanning from URLs.

Capabilities

Real-time Barcode Scanning

Configure the camera component to detect barcodes in real-time during camera preview.

/**
 * Configure barcode scanning settings for the camera
 */
interface BarcodeSettings {
  /**
   * Array of barcode types to detect
   */
  barcodeTypes: BarcodeType[];
}

/**
 * Callback for barcode detection events
 * @param result The detected barcode information
 */
type BarcodeScanningCallback = (result: BarcodeScanningResult) => void;

Static Image Barcode Scanning

Scan barcodes from static images via URL.

/**
 * Scan barcodes from an image at the given URL
 * @param url URL of the image to scan
 * @param barcodeTypes Array of barcode types to detect (defaults to ['qr'])
 * @returns Promise resolving to array of detected barcodes
 */
function scanFromURLAsync(
  url: string,
  barcodeTypes?: BarcodeType[]
): Promise<BarcodeScanningResult[]>;

Modern Scanner (iOS Only)

Native iOS barcode scanner with advanced features.

/**
 * Launch the modern iOS barcode scanner
 * @returns Promise resolving when scanner is launched
 */
launchModernScanner(): Promise<void>;

/**
 * Launch the barcode scanner with configuration options (iOS only)
 * @param options Scanning configuration options
 * @returns Promise resolving when scanner launches
 */
launchScanner(options?: ScanningOptions): Promise<void>;

/**
 * Dismiss the active barcode scanner (iOS only)
 * @returns Promise resolving when scanner is dismissed
 */
dismissScanner(): Promise<void>;

/**
 * Configuration options for iOS barcode scanner
 */
interface ScanningOptions {
  /**
   * Types of barcodes to scan for
   */
  barcodeTypes: BarcodeType[];
  
  /**
   * Enable pinch-to-zoom gesture
   * @default true
   */
  isPinchToZoomEnabled?: boolean;
  
  /**
   * Show guidance text over video
   * @default true
   */
  isGuidanceEnabled?: boolean;
  
  /**
   * Highlight recognized items
   * @default false
   */
  isHighlightingEnabled?: boolean;
}

Barcode Result Structure

Complete structure of barcode scanning results.

interface BarcodeScanningResult {
  /**
   * Detected barcode type
   */
  type: string;
  
  /**
   * Parsed barcode data
   */
  data: string;
  
  /**
   * Raw barcode data (Android only)
   */
  raw?: string;
  
  /**
   * Corner points of the barcode bounding box
   * Order varies by platform:
   * - Android: topLeft, topRight, bottomRight, bottomLeft
   * - iOS: bottomLeft, bottomRight, topLeft, topRight  
   * - Web: topLeft, bottomLeft, topRight, bottomRight
   */
  cornerPoints: BarcodePoint[];
  
  /**
   * Bounding box of the detected barcode
   */
  bounds: BarcodeBounds;
  
  /**
   * Platform-specific extra information (Android only)
   */
  extra?: AndroidBarcode;
}

interface BarcodePoint {
  x: number;
  y: number;
}

interface BarcodeBounds {
  /**
   * Origin point of bounding box
   */
  origin: BarcodePoint;
  
  /**
   * Size of bounding box
   */
  size: BarcodeSize;
}

interface BarcodeSize {
  width: number;
  height: number;
}

Supported Barcode Types

All supported barcode formats across platforms.

type BarcodeType =
  | 'aztec'
  | 'ean13'
  | 'ean8'
  | 'qr'
  | 'pdf417'
  | 'upc_e'
  | 'datamatrix'
  | 'code39'
  | 'code93'
  | 'itf14'
  | 'codabar'
  | 'code128'
  | 'upc_a';

Android Enhanced Barcode Data

Extended barcode information available on Android platform.

/**
 * Enhanced barcode information for Android platform
 */
type AndroidBarcode =
  | ContactInfoBarcode
  | GeoPointBarcode
  | SmsBarcode
  | UrlBarcode
  | CalendarEventBarcode
  | DriverLicenseBarcode
  | EmailBarcode
  | PhoneBarcode
  | WifiBarcode;

interface ContactInfoBarcode {
  type: 'contactInfo';
  firstName?: string;
  middleName?: string;
  lastName?: string;
  title?: string;
  organization?: string;
  email?: string;
  phone?: string;
  url?: string;
  address?: string;
}

interface GeoPointBarcode {
  type: 'geoPoint';
  lat: string;
  lng: string;
}

interface SmsBarcode {
  type: 'sms';
  phoneNumber?: string;
  message?: string;
}

interface UrlBarcode {
  type: 'url';
  url?: string;
}

interface CalendarEventBarcode {
  type: 'calendarEvent';
  summary?: string;
  description?: string;
  location?: string;
  start?: string;
  end?: string;
}

interface DriverLicenseBarcode {
  type: 'driverLicense';
  firstName?: string;
  middleName?: string;
  lastName?: string;
  licenseNumber?: string;
  expiryDate?: string;
  issueDate?: string;
  addressStreet?: string;
  addressCity?: string;
  addressState?: string;
}

interface EmailBarcode {
  type: 'email';
  address?: string;
  subject?: string;
  body?: string;
}

interface PhoneBarcode {
  type: 'phone';
  number?: string;
  phoneNumberType?: string;
}

interface WifiBarcode {
  type: 'wifi';
  ssid?: string;
  password?: string;
  encryptionType?: string;
}

Usage Examples

Basic QR Code Scanning

import React, { useState } from 'react';
import { View, Text, StyleSheet, Alert } from 'react-native';
import { CameraView, BarcodeScanningResult } from 'expo-camera';

export function QRCodeScanner() {
  const [scannedData, setScannedData] = useState<string | null>(null);

  const handleBarcodeScanned = (result: BarcodeScanningResult) => {
    console.log('Barcode scanned:', result);
    setScannedData(result.data);
    
    Alert.alert(
      'QR Code Detected',
      result.data,
      [
        { text: 'OK', onPress: () => setScannedData(null) }
      ]
    );
  };

  return (
    <View style={styles.container}>
      <CameraView
        style={styles.camera}
        facing="back"
        onBarcodeScanned={handleBarcodeScanned}
        barcodeScannerSettings={{
          barcodeTypes: ['qr']
        }}
      />
      
      {scannedData && (
        <View style={styles.resultContainer}>
          <Text style={styles.resultText}>
            Last scanned: {scannedData}
          </Text>
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  camera: { flex: 1 },
  resultContainer: {
    position: 'absolute',
    bottom: 100,
    left: 20,
    right: 20,
    backgroundColor: 'rgba(0,0,0,0.8)',
    padding: 15,
    borderRadius: 10,
  },
  resultText: {
    color: 'white',
    textAlign: 'center',
  },
});

Multi-Format Barcode Scanner

import React, { useState } from 'react';
import { View, Text, StyleSheet, FlatList } from 'react-native';
import { CameraView, BarcodeScanningResult, BarcodeType } from 'expo-camera';

interface ScannedBarcode {
  id: string;
  type: string;
  data: string;
  timestamp: Date;
}

export function MultiFormatBarcodeScanner() {
  const [scannedBarcodes, setScannedBarcodes] = useState<ScannedBarcode[]>([]);

  const supportedTypes: BarcodeType[] = [
    'qr',
    'ean13',
    'ean8',
    'code128',
    'code39',
    'upc_a',
    'upc_e',
    'pdf417',
    'aztec',
    'datamatrix'
  ];

  const handleBarcodeScanned = (result: BarcodeScanningResult) => {
    // Avoid duplicate scans of the same barcode
    const isDuplicate = scannedBarcodes.some(
      barcode => barcode.data === result.data && barcode.type === result.type
    );

    if (!isDuplicate) {
      const newBarcode: ScannedBarcode = {
        id: `${Date.now()}-${result.type}`,
        type: result.type,
        data: result.data,
        timestamp: new Date(),
      };

      setScannedBarcodes(prev => [newBarcode, ...prev].slice(0, 10)); // Keep last 10
      
      console.log('New barcode detected:', {
        type: result.type,
        data: result.data,
        bounds: result.bounds,
        cornerPoints: result.cornerPoints,
      });
    }
  };

  const renderBarcodeItem = ({ item }: { item: ScannedBarcode }) => (
    <View style={styles.barcodeItem}>
      <Text style={styles.barcodeType}>{item.type.toUpperCase()}</Text>
      <Text style={styles.barcodeData}>{item.data}</Text>
      <Text style={styles.barcodeTime}>
        {item.timestamp.toLocaleTimeString()}
      </Text>
    </View>
  );

  return (
    <View style={styles.container}>
      <CameraView
        style={styles.camera}
        facing="back"
        onBarcodeScanned={handleBarcodeScanned}
        barcodeScannerSettings={{
          barcodeTypes: supportedTypes
        }}
      />
      
      <View style={styles.overlay}>
        <Text style={styles.instructions}>
          Point camera at barcode or QR code
        </Text>
        <Text style={styles.supportedFormats}>
          Supports: QR, EAN, UPC, Code128, Code39, PDF417, Aztec, DataMatrix
        </Text>
      </View>

      {scannedBarcodes.length > 0 && (
        <View style={styles.resultsContainer}>
          <Text style={styles.resultsTitle}>Recent Scans:</Text>
          <FlatList
            data={scannedBarcodes}
            renderItem={renderBarcodeItem}
            keyExtractor={item => item.id}
            style={styles.resultsList}
          />
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  camera: { flex: 1 },
  overlay: {
    position: 'absolute',
    top: 100,
    left: 20,
    right: 20,
    alignItems: 'center',
  },
  instructions: {
    color: 'white',
    fontSize: 18,
    fontWeight: 'bold',
    textAlign: 'center',
    backgroundColor: 'rgba(0,0,0,0.7)',
    padding: 10,
    borderRadius: 10,
  },
  supportedFormats: {
    color: 'white',
    fontSize: 12,
    textAlign: 'center',
    backgroundColor: 'rgba(0,0,0,0.5)',
    padding: 5,
    borderRadius: 5,
    marginTop: 10,
  },
  resultsContainer: {
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: 'rgba(255,255,255,0.95)',
    maxHeight: 200,
  },
  resultsTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    padding: 10,
    textAlign: 'center',
  },
  resultsList: {
    maxHeight: 150,
  },
  barcodeItem: {
    padding: 10,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  barcodeType: {
    fontSize: 12,
    fontWeight: 'bold',
    color: '#007AFF',
  },
  barcodeData: {
    fontSize: 14,
    marginVertical: 2,
  },
  barcodeTime: {
    fontSize: 10,
    color: '#666',
  },
});

Static Image Scanning

import React, { useState } from 'react';
import { View, TextInput, TouchableOpacity, Text, StyleSheet, Alert } from 'react-native';
import { scanFromURLAsync, BarcodeScanningResult, BarcodeType } from 'expo-camera';

export function StaticImageScanner() {
  const [imageUrl, setImageUrl] = useState('');
  const [isScanning, setIsScanning] = useState(false);
  const [results, setResults] = useState<BarcodeScanningResult[]>([]);

  const scanImageURL = async () => {
    if (!imageUrl.trim()) {
      Alert.alert('Error', 'Please enter an image URL');
      return;
    }

    setIsScanning(true);
    try {
      const barcodeTypes: BarcodeType[] = [
        'qr', 'ean13', 'ean8', 'code128', 'code39', 'upc_a', 'upc_e'
      ];
      
      const detectedBarcodes = await scanFromURLAsync(imageUrl, barcodeTypes);
      
      setResults(detectedBarcodes);
      
      if (detectedBarcodes.length === 0) {
        Alert.alert('No Barcodes Found', 'No barcodes were detected in the image');
      } else {
        Alert.alert(
          'Barcodes Found', 
          `Found ${detectedBarcodes.length} barcode(s)`
        );
      }
    } catch (error) {
      Alert.alert('Scan Failed', `Error scanning image: ${error.message}`);
    } finally {
      setIsScanning(false);
    }
  };

  const scanQROnly = async () => {
    if (!imageUrl.trim()) return;

    setIsScanning(true);
    try {
      // Scan for QR codes only (iOS limitation note)
      const qrCodes = await scanFromURLAsync(imageUrl, ['qr']);
      setResults(qrCodes);
      
      if (qrCodes.length > 0) {
        Alert.alert('QR Code Found', qrCodes[0].data);
      } else {
        Alert.alert('No QR Code Found', 'No QR codes detected in the image');
      }
    } catch (error) {
      Alert.alert('Scan Failed', error.message);
    } finally {
      setIsScanning(false);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Static Image Barcode Scanner</Text>
      
      <TextInput
        style={styles.input}
        placeholder="Enter image URL"
        value={imageUrl}
        onChangeText={setImageUrl}
        multiline
      />
      
      <View style={styles.buttonContainer}>
        <TouchableOpacity
          style={[styles.button, isScanning && styles.disabledButton]}
          onPress={scanImageURL}
          disabled={isScanning}
        >
          <Text style={styles.buttonText}>
            {isScanning ? 'Scanning...' : 'Scan All Types'}
          </Text>
        </TouchableOpacity>
        
        <TouchableOpacity
          style={[styles.button, isScanning && styles.disabledButton]}
          onPress={scanQROnly}
          disabled={isScanning}
        >
          <Text style={styles.buttonText}>
            {isScanning ? 'Scanning...' : 'Scan QR Only'}
          </Text>
        </TouchableOpacity>
      </View>

      {results.length > 0 && (
        <View style={styles.resultsContainer}>
          <Text style={styles.resultsTitle}>Scan Results:</Text>
          {results.map((result, index) => (
            <View key={index} style={styles.resultItem}>
              <Text style={styles.resultType}>Type: {result.type}</Text>
              <Text style={styles.resultData}>Data: {result.data}</Text>
              {result.bounds && (
                <Text style={styles.resultBounds}>
                  Bounds: {result.bounds.size.width} x {result.bounds.size.height}
                </Text>
              )}
            </View>
          ))}
        </View>
      )}

      <Text style={styles.note}>
        Note: On iOS, only QR codes are supported for URL scanning
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    textAlign: 'center',
    marginBottom: 20,
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    padding: 15,
    backgroundColor: 'white',
    marginBottom: 20,
    minHeight: 100,
    textAlignVertical: 'top',
  },
  buttonContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 20,
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 15,
    borderRadius: 8,
    flex: 0.48,
  },
  disabledButton: {
    backgroundColor: '#ccc',
  },
  buttonText: {
    color: 'white',
    textAlign: 'center',
    fontWeight: 'bold',
  },
  resultsContainer: {
    backgroundColor: 'white',
    borderRadius: 8,
    padding: 15,
    marginBottom: 20,
  },
  resultsTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  resultItem: {
    marginBottom: 15,
    paddingBottom: 15,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  resultType: {
    fontWeight: 'bold',
    color: '#007AFF',
  },
  resultData: {
    marginTop: 5,
  },
  resultBounds: {
    marginTop: 5,
    fontSize: 12,
    color: '#666',
  },
  note: {
    fontSize: 12,
    color: '#666',
    textAlign: 'center',
    fontStyle: 'italic',
  },
});

iOS Modern Scanner

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

export function ModernBarcodeScanner() {
  const cameraRef = React.useRef<CameraView>(null);

  // Listen for modern scanner results
  useEffect(() => {
    if (Platform.OS !== 'ios') return;

    // Set up event listener for modern barcode scanning
    const subscription = CameraManager.addListener(
      'onModernBarcodeScanned',
      (event: ScanningResult) => {
        console.log('Modern scanner result:', event);
        // Handle the scanned result
      }
    );

    return () => subscription?.remove();
  }, []);

  const launchModernScanner = async () => {
    if (Platform.OS !== 'ios') {
      console.warn('Modern scanner is only available on iOS');
      return;
    }

    if (!cameraRef.current) return;

    try {
      await cameraRef.current.launchModernScanner();
    } catch (error) {
      console.error('Failed to launch modern scanner:', error);
    }
  };

  const launchScannerWithOptions = async () => {
    if (Platform.OS !== 'ios') return;

    try {
      const options: ScanningOptions = {
        barcodeTypes: ['qr', 'ean13', 'code128'],
        isPinchToZoomEnabled: true,
        isGuidanceEnabled: true,
        isHighlightingEnabled: true,
      };

      // Note: This would typically be called on a CameraManager instance
      await CameraManager.launchScanner(options);
    } catch (error) {
      console.error('Failed to launch scanner with options:', error);
    }
  };

  const dismissScanner = async () => {
    if (Platform.OS !== 'ios') return;

    try {
      await CameraManager.dismissScanner();
    } catch (error) {
      console.error('Failed to dismiss scanner:', error);
    }
  };

  if (Platform.OS !== 'ios') {
    return (
      <View style={styles.container}>
        <Text style={styles.unavailable}>
          Modern scanner is only available on iOS
        </Text>
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <CameraView
        ref={cameraRef}
        style={styles.camera}
        facing="back"
      />
      
      <View style={styles.controls}>
        <TouchableOpacity style={styles.button} onPress={launchModernScanner}>
          <Text style={styles.buttonText}>Launch Modern Scanner</Text>
        </TouchableOpacity>
        
        <TouchableOpacity style={styles.button} onPress={launchScannerWithOptions}>
          <Text style={styles.buttonText}>Launch with Options</Text>
        </TouchableOpacity>
        
        <TouchableOpacity style={styles.button} onPress={dismissScanner}>
          <Text style={styles.buttonText}>Dismiss Scanner</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  camera: { flex: 1 },
  unavailable: {
    textAlign: 'center',
    fontSize: 16,
    color: '#666',
    marginTop: 100,
  },
  controls: {
    position: 'absolute',
    bottom: 50,
    left: 20,
    right: 20,
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 15,
    borderRadius: 8,
    marginBottom: 10,
  },
  buttonText: {
    color: 'white',
    textAlign: 'center',
    fontWeight: 'bold',
  },
});