# React Native PDF417 Scanner SDK

A comprehensive React Native SDK for live PDF417 barcode scanning with **native camera view** using Android implementation and ZXing library.

## Features

- **🎥 Live Camera View**: Real-time camera preview with native Android TextureView
- **📱 Live PDF417 Scanning**: Real-time barcode detection using device camera
- **⚡ Native Performance**: Optimized native Android implementation with ZXing
- **🎛️ Camera Management**: Automatic camera permission handling and lifecycle management
- **🔦 Advanced Controls**: Autofocus, torch/flashlight control, scan timeout
- **📡 Event-Driven API**: Clean React Native bridge with events and callbacks
- **🛡️ Error Handling**: Comprehensive error reporting and recovery
- **📝 TypeScript Support**: Full TypeScript definitions included

## What's New in v1.4.0

- **🔧 Camera Fixes**: Resolved "Failed to start preview" errors with enhanced error handling
- **🔄 Retry Logic**: Automatic camera initialization retry (up to 3 attempts)
- **📱 Better Compatibility**: Improved Camera2 API configuration for various devices
- **🛠️ Enhanced Debugging**: Comprehensive logging and troubleshooting guide
- **⚡ Stability Improvements**: Better threading and surface texture handling

## Previous Updates (v1.2.0)

- **Native Camera View Component**: Show live camera feed instead of black screen
- **Real-time Preview**: See exactly what the camera sees while scanning
- **Overlay Support**: Add custom UI overlays on top of camera view
- **Better UX**: Professional scanning experience with live preview

## Installation

### 1. Install the Package

```bash
npm install react-native-pdf417-scanner
# or
yarn add react-native-pdf417-scanner
```

### 2. Android Configuration

#### Add to `android/settings.gradle`:
```gradle
include ':react-native-pdf417-scanner'
project(':react-native-pdf417-scanner').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-pdf417-scanner/android')
```

#### Add to `android/app/build.gradle`:
```gradle
dependencies {
    implementation project(':react-native-pdf417-scanner')
    // ... other dependencies
}
```

#### Register the Package in `MainApplication.java/kt`:
```kotlin
import com.pdf417scanner.PDF417ScannerPackage

// In the packages list:
add(PDF417ScannerPackage())
```

#### Add Permissions to `AndroidManifest.xml`:
```xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-permission android:name="android.permission.VIBRATE" />

<uses-feature
    android:name="android.hardware.camera"
    android:required="true" />
<uses-feature
    android:name="android.hardware.camera.autofocus"
    android:required="false" />
<uses-feature
    android:name="android.hardware.camera.flash"
    android:required="false" />
```

## Usage

### Method 1: With Native Camera View (Recommended)

```typescript
import React, { useEffect, useState } from 'react';
import { View, Modal, TouchableOpacity, Text, StyleSheet } from 'react-native';
import PDF417Scanner, { 
  PDF417ScanResult, 
  PDF417ScanError, 
  PDF417CameraView 
} from 'react-native-pdf417-scanner';

const ScannerWithCameraView = () => {
  const [showScanner, setShowScanner] = useState(false);
  const [torchEnabled, setTorchEnabled] = useState(false);
  const [cameraReady, setCameraReady] = useState(false);

  useEffect(() => {
    const scanResultUnsubscribe = PDF417Scanner.onScanResult(handleScanResult);
    const scanErrorUnsubscribe = PDF417Scanner.onScanError(handleScanError);

    return () => {
      scanResultUnsubscribe();
      scanErrorUnsubscribe();
    };
  }, []);

  const handleScanResult = (result: PDF417ScanResult) => {
    console.log('Scanned:', result.data);
    setShowScanner(false);
    // Handle scan result
  };

  const handleScanError = (error: PDF417ScanError) => {
    console.error('Scan error:', error);
  };

  const startScanning = async () => {
    setShowScanner(true);
    try {
      await PDF417Scanner.startScanning({
        enableAutoFocus: true,
        enableTorch: torchEnabled,
        scanTimeout: 30000,
        enableBeep: true,
        enableVibration: true,
      });
    } catch (error) {
      console.error('Failed to start scanning:', error);
      setShowScanner(false);
    }
  };

  const stopScanning = async () => {
    try {
      await PDF417Scanner.stopScanning();
      setShowScanner(false);
      setCameraReady(false);
    } catch (error) {
      console.error('Failed to stop scanning:', error);
    }
  };

  return (
    <View style={styles.container}>
      <TouchableOpacity style={styles.scanButton} onPress={startScanning}>
        <Text style={styles.scanButtonText}>Start Scanning</Text>
      </TouchableOpacity>

      <Modal visible={showScanner} animationType="slide">
        <View style={styles.scannerContainer}>
          {/* Native Camera View */}
          <PDF417CameraView
            style={styles.cameraView}
            isActive={showScanner}
            torchEnabled={torchEnabled}
            onCameraReady={() => setCameraReady(true)}
            onCameraError={(event) => {
              console.error('Camera error:', event.nativeEvent.error);
              setShowScanner(false);
            }}
          />

          {/* Overlay UI */}
          <View style={styles.overlay}>
            {/* Header */}
            <View style={styles.header}>
              <TouchableOpacity onPress={stopScanning}>
                <Text style={styles.closeButton}>Close</Text>
              </TouchableOpacity>
              <Text style={styles.title}>Scan PDF417</Text>
              <TouchableOpacity onPress={() => setTorchEnabled(!torchEnabled)}>
                <Text style={styles.torchButton}>
                  {torchEnabled ? 'Flash Off' : 'Flash On'}
                </Text>
              </TouchableOpacity>
            </View>

            {/* Scan Frame */}
            <View style={styles.scanFrame}>
              <View style={styles.frameCorner} />
              <View style={[styles.frameCorner, styles.topRight]} />
              <View style={[styles.frameCorner, styles.bottomLeft]} />
              <View style={[styles.frameCorner, styles.bottomRight]} />
            </View>

            {/* Instructions */}
            <View style={styles.instructions}>
              <Text style={styles.instructionText}>
                {cameraReady 
                  ? 'Point camera at PDF417 barcode' 
                  : 'Starting camera...'}
              </Text>
            </View>
          </View>
        </View>
      </Modal>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  scanButton: {
    backgroundColor: '#2196F3',
    paddingHorizontal: 30,
    paddingVertical: 15,
    borderRadius: 8,
  },
  scanButtonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: 'bold',
  },
  scannerContainer: {
    flex: 1,
  },
  cameraView: {
    flex: 1,
  },
  overlay: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  },
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingHorizontal: 20,
    paddingTop: 50,
    paddingBottom: 20,
    backgroundColor: 'rgba(0,0,0,0.7)',
  },
  closeButton: {
    color: '#fff',
    fontSize: 16,
  },
  title: {
    color: '#fff',
    fontSize: 18,
    fontWeight: 'bold',
  },
  torchButton: {
    color: '#fff',
    fontSize: 16,
  },
  scanFrame: {
    position: 'absolute',
    top: '50%',
    left: '50%',
    width: 250,
    height: 150,
    marginTop: -75,
    marginLeft: -125,
  },
  frameCorner: {
    position: 'absolute',
    width: 20,
    height: 20,
    borderColor: '#2196F3',
    borderWidth: 3,
    borderRightColor: 'transparent',
    borderBottomColor: 'transparent',
  },
  topRight: {
    top: 0,
    right: 0,
    borderTopColor: '#2196F3',
    borderRightColor: '#2196F3',
    borderLeftColor: 'transparent',
    borderBottomColor: 'transparent',
  },
  bottomLeft: {
    bottom: 0,
    left: 0,
    borderBottomColor: '#2196F3',
    borderLeftColor: '#2196F3',
    borderTopColor: 'transparent',
    borderRightColor: 'transparent',
  },
  bottomRight: {
    bottom: 0,
    right: 0,
    borderBottomColor: '#2196F3',
    borderRightColor: '#2196F3',
    borderTopColor: 'transparent',
    borderLeftColor: 'transparent',
  },
  instructions: {
    position: 'absolute',
    bottom: 100,
    left: 0,
    right: 0,
    alignItems: 'center',
  },
  instructionText: {
    color: '#fff',
    fontSize: 16,
    textAlign: 'center',
    backgroundColor: 'rgba(0,0,0,0.7)',
    paddingHorizontal: 20,
    paddingVertical: 10,
    borderRadius: 8,
  },
});
```

### Method 2: Basic Implementation (Background Scanning)

```typescript
import React, { useEffect, useState } from 'react';
import { View, Button, Alert } from 'react-native';
import PDF417Scanner, { PDF417ScanResult, PDF417ScanError } from 'react-native-pdf417-scanner';

const BasicScannerComponent = () => {
  const [isScanning, setIsScanning] = useState(false);

  useEffect(() => {
    // Set up event listeners
    const scanResultUnsubscribe = PDF417Scanner.onScanResult(handleScanResult);
    const scanErrorUnsubscribe = PDF417Scanner.onScanError(handleScanError);
    const cameraReadyUnsubscribe = PDF417Scanner.onCameraReady(handleCameraReady);

    return () => {
      scanResultUnsubscribe();
      scanErrorUnsubscribe();
      cameraReadyUnsubscribe();
    };
  }, []);

  const handleScanResult = (result: PDF417ScanResult) => {
    console.log('Scanned:', result.data);
    Alert.alert('Scanned', result.data);
  };

  const handleScanError = (error: PDF417ScanError) => {
    console.error('Scan error:', error);
    Alert.alert('Error', error.message);
  };

  const handleCameraReady = () => {
    console.log('Camera is ready');
  };

  const startScanning = async () => {
    try {
      await PDF417Scanner.startScanning({
        enableAutoFocus: true,
        enableTorch: false,
        scanTimeout: 30000,
        enableBeep: true,
        enableVibration: true,
      });
      setIsScanning(true);
    } catch (error) {
      Alert.alert('Error', `Failed to start scanning: ${error}`);
    }
  };

  const stopScanning = async () => {
    try {
      await PDF417Scanner.stopScanning();
      setIsScanning(false);
    } catch (error) {
      Alert.alert('Error', `Failed to stop scanning: ${error}`);
    }
  };

  return (
    <View>
      <Button
        title={isScanning ? 'Stop Scanning' : 'Start Scanning'}
        onPress={isScanning ? stopScanning : startScanning}
      />
    </View>
  );
};
```

## API Reference

### Components

#### `PDF417CameraView`
Native camera view component that displays live camera feed.

**Props:**
```typescript
interface PDF417CameraViewProps {
  style?: ViewStyle;                    // View styling
  isActive?: boolean;                   // Whether camera should be active
  torchEnabled?: boolean;               // Flash/torch state
  onCameraReady?: () => void;           // Called when camera is ready
  onCameraError?: (event: {             // Called on camera errors
    nativeEvent: { error: string }
  }) => void;
}
```

**Example:**
```typescript
<PDF417CameraView
  style={{ flex: 1 }}
  isActive={true}
  torchEnabled={false}
  onCameraReady={() => console.log('Camera ready')}
  onCameraError={(e) => console.log('Error:', e.nativeEvent.error)}
/>
```

### Methods

#### `startScanning(config?: PDF417ScannerConfig): Promise<void>`
Starts the PDF417 scanning process.

**Parameters:**
- `config` (optional): Configuration object for scanning behavior

**Configuration Options:**
```typescript
interface PDF417ScannerConfig {
  enableAutoFocus?: boolean;     // Default: true
  enableTorch?: boolean;         // Default: false
  scanTimeout?: number;          // Default: 30000 (30 seconds)
  enableBeep?: boolean;          // Default: true
  enableVibration?: boolean;     // Default: true
}
```

#### `stopScanning(): Promise<void>`
Stops the scanning process and releases camera resources.

#### `setTorchEnabled(enabled: boolean): Promise<void>`
Controls the device flashlight/torch.

#### `isCurrentlyScanning(): boolean`
Returns the current scanning state.

#### `requestCameraPermission(): Promise<boolean>`
Requests camera permission from the user.

#### `hasCameraPermission(): Promise<boolean>`
Checks if camera permission is granted.

### Events

#### `onScanResult(callback: (result: PDF417ScanResult) => void): () => void`
Listens for successful scan results.

**Result Object:**
```typescript
interface PDF417ScanResult {
  data: string;        // The decoded barcode data
  format: string;      // Barcode format (PDF_417)
  timestamp: number;   // Scan timestamp
}
```

#### `onScanError(callback: (error: PDF417ScanError) => void): () => void`
Listens for scan errors.

**Error Object:**
```typescript
interface PDF417ScanError {
  code: string;        // Error code
  message: string;     // Error description
}
```

#### `onCameraReady(callback: () => void): () => void`
Listens for camera ready events.

## Error Codes

| Code | Description |
|------|-------------|
| `PERMISSION_DENIED` | Camera permission not granted |
| `CAMERA_ERROR` | Camera hardware or configuration error |
| `START_SCANNING_ERROR` | Failed to start scanning process |
| `STOP_SCANNING_ERROR` | Failed to stop scanning process |
| `TORCH_ERROR` | Failed to control flashlight |

## Performance Optimization

### Best Practices

1. **Lifecycle Management**: Always stop scanning when the component unmounts
2. **Permission Handling**: Check permissions before starting scanning
3. **Event Cleanup**: Unsubscribe from events to prevent memory leaks
4. **Error Recovery**: Implement proper error handling and recovery mechanisms

### Example with Lifecycle Management

```typescript
import React, { useEffect, useRef } from 'react';
import { AppState } from 'react-native';
import PDF417Scanner from 'react-native-pdf417-scanner';

const ScannerWithLifecycle = () => {
  const appState = useRef(AppState.currentState);
  const [isScanning, setIsScanning] = useState(false);

  useEffect(() => {
    const handleAppStateChange = (nextAppState: string) => {
      if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
        // App has come to the foreground
        if (isScanning) {
          restartScanning();
        }
      } else if (nextAppState.match(/inactive|background/)) {
        // App has gone to the background
        if (isScanning) {
          PDF417Scanner.stopScanning();
        }
      }
      appState.current = nextAppState;
    };

    const subscription = AppState.addEventListener('change', handleAppStateChange);
    return () => subscription?.remove();
  }, [isScanning]);

  // ... rest of component
};
```

## Troubleshooting

### Common Issues

1. **"Failed to start preview" Error**
   - **Cause**: Camera initialization issues, surface texture not ready, or device compatibility
   - **Solution**: The package now includes automatic retry logic (up to 3 attempts)
   - **Manual Fix**: Restart the app, check camera permissions, or try on a different device

2. **Camera Permission Denied**
   - Ensure permissions are declared in AndroidManifest.xml
   - Request permissions before starting scanning
   - Handle permission denial gracefully

3. **Camera Already in Use**
   - Close other camera applications
   - Restart the device if the issue persists
   - Check if another part of your app is using the camera

4. **Build Errors**
   - Verify all Gradle configurations are correct
   - Ensure ZXing dependencies are properly resolved
   - Clean and rebuild the project

5. **Scanner Not Working**
   - Check device camera functionality
   - Verify proper lighting conditions
   - Ensure PDF417 barcode is clearly visible

### Camera Error Recovery

The package now includes enhanced error recovery:

```typescript
const handleScanError = (error: PDF417ScanError) => {
  if (error.code === 'CAMERA_ERROR') {
    if (error.message.includes('permission')) {
      // Handle permission issues
      requestCameraPermission();
    } else if (error.message.includes('preview')) {
      // Handle camera preview issues - retry automatically
      setTimeout(() => startScanning(), 1000);
    }
  }
};
```

### Device-Specific Issues

- **Samsung Devices**: Disable "Camera Assistant" if experiencing issues
- **Huawei/Honor**: Check "Phone Manager" permissions
- **Xiaomi**: Grant "Display pop-up windows" permission in MIUI settings

### Debug Mode

Enable debug logging by checking Android logs:
```bash
adb logcat | grep -E "(PDF417|Camera|ERROR)"
```

## Requirements

- React Native >= 0.60
- Android API Level >= 21
- Camera hardware with autofocus support (recommended)

## License

MIT License - see LICENSE file for details.

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request

## Support

For issues and questions:
- Create an issue on GitHub
- Check existing documentation
- Review the demo application for implementation examples