react-native-native-doc-scanner
Version:
High-performance cross-platform document scanner with session-based crash recovery, native bridge architecture, and TurboModule compatibility
536 lines (402 loc) • 18.2 kB
Markdown
//badge.fury.io/js/react-native-native-doc-scanner.svg)](https://badge.fury.io/js/react-native-native-doc-scanner)
[](https://opensource.org/licenses/MIT)
[](https://github.com/rahulunni73/react-native-native-doc-scanner)
High-performance cross-platform document scanner for React Native with **session-based crash recovery**, native bridge architecture, and TurboModule compatibility.
- 📄 **Cross-Platform Document Scanning** - iOS VisionKit + Android ML Kit
- 🔄 **Session-Based Crash Recovery** - Automatic recovery of scan results after app crashes or memory kills
- 🚀 **Architecture Agnostic** - Works with both Legacy Bridge and New Architecture (TurboModules)
- 📋 **Multi-Page Scanning** - Scan up to 50 pages in a single session (or unlimited with -1)
- 🎯 **PDF Generation** - Automatic PDF creation from scanned documents
- 📱 **Gallery Import** - Import images from gallery (Android only)
- ⚡ **High Performance** - Native implementation for optimal speed
- 🔧 **TypeScript Support** - Full type definitions included
- 🎨 **Customizable** - Multiple scanner modes and quality settings
- 📏 **Size Management** - Built-in file size validation with configurable limits
- 🛡️ **Process Kill Protection** - Handles Android memory pressure and process termination gracefully
## 📦 Installation
```bash
npm install react-native-native-doc-scanner
```
### iOS Setup
Add the following permissions to your `Info.plist`:
```xml
<key>NSCameraUsageDescription</key>
<string>This app needs access to camera to scan documents</string>
```
### Android Setup
1. **Add permissions** to your `android/app/src/main/AndroidManifest.xml`:
```xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
```
2. **Add the ScannerActivity** to your `android/app/src/main/AndroidManifest.xml` inside the `<application>` tag:
```xml
<application>
<!-- Your existing activities -->
<!-- Document Scanner Activity -->
<activity
android:name="com.nativedocscanner.ScannerActivity"
android:exported="false"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
</application>
```
3. **For React Native 0.60+**: The library should auto-link. For older versions, manually link the library.
```typescript
import NativeDocScanner, { ScannerConfig, SCANNER_MODE } from 'react-native-native-doc-scanner';
const config: ScannerConfig = {
scannerMode: SCANNER_MODE.FULL,
isGalleryImportRequired: true, // Android only
pageLimit: 5,
maxSizeLimit: 50 * 1024 * 1024 // 50MB limit (optional)
};
// Callback-based API
NativeDocScanner.scanDocument(
config,
(result) => {
console.log('Scan successful:', result);
// result.imagePaths - paths to scanned images
// result.PdfUri - path to generated PDF
// result.PdfPageCount - number of pages scanned
},
(error) => {
console.log('Scan failed:', error);
}
);
```
```typescript
import NativeDocScanner, { ScannerConfig, SCANNER_MODE } from 'react-native-native-doc-scanner';
const scanDocuments = async () => {
try {
const config: ScannerConfig = {
scannerMode: SCANNER_MODE.FULL,
isGalleryImportRequired: false,
pageLimit: 3,
maxSizeLimit: 100 * 1024 * 1024 // 100MB limit
};
const result = await NativeDocScanner.scanDocumentAsync(config);
console.log('Scanned documents:', result);
console.log('Total size:', result.totalImageSize, 'bytes');
console.log('PDF size:', result.pdfSize, 'bytes');
} catch (error) {
console.error('Scanning failed:', error);
}
};
```
This library includes a **session-based crash recovery system** that automatically handles app crashes and memory kills during scanning operations, ensuring users never lose their scan results.
The crash recovery system tracks scan sessions with timestamps and persists results to device storage. When your app restarts after a crash or memory kill, you can automatically recover the scan results from the interrupted session.
**Key Features:**
- ✅ **Session-based tracking** - Only recovers results from the current interrupted scan session
- ✅ **Cross-platform** - Works on both iOS and Android
- ✅ **Automatic persistence** - No manual intervention required
- ✅ **Memory kill protection** - Specifically designed for Android memory pressure scenarios
- ✅ **Clean state management** - Clears recovery data after successful delivery
```typescript
import NativeDocScanner, { CrashRecoveryResult } from 'react-native-native-doc-scanner';
// Check for crash recovery when your app starts or component mounts
const checkForRecovery = async () => {
try {
const recoveryResult: CrashRecoveryResult | null = await NativeDocScanner.checkForCrashRecovery();
if (recoveryResult && recoveryResult.fromCrashRecovery) {
console.log('🔄 Recovered scan result from interrupted session!');
// Parse the recovered result
const scanResult = JSON.parse(recoveryResult.scanResult);
// Handle the recovered result (same as normal scan result)
console.log('Recovered documents:', scanResult.imagePaths);
console.log('Recovered PDF:', scanResult.PdfUri);
// Show user notification about recovery
Alert.alert(
'Scan Recovered',
'Your previous scan was automatically recovered!',
[{ text: 'OK', onPress: () => handleRecoveredScan(scanResult) }]
);
} else {
console.log('No pending scan results to recover');
}
} catch (error) {
console.error('Crash recovery check failed:', error);
}
};
// Call this when your app starts
useEffect(() => {
checkForRecovery();
}, []);
```
```typescript
import React, { useEffect, useState } from 'react';
import { View, Text, Alert, TouchableOpacity } from 'react-native';
import NativeDocScanner, { ScannerConfig, SCANNER_MODE } from 'react-native-native-doc-scanner';
const DocumentScannerScreen = () => {
const [hasRecoveredResult, setHasRecoveredResult] = useState(false);
const [recoveredResult, setRecoveredResult] = useState(null);
useEffect(() => {
checkForCrashRecovery();
}, []);
const checkForCrashRecovery = async () => {
try {
const recovery = await NativeDocScanner.checkForCrashRecovery();
if (recovery?.fromCrashRecovery) {
const scanResult = JSON.parse(recovery.scanResult);
setRecoveredResult(scanResult);
setHasRecoveredResult(true);
// Show recovery banner
Alert.alert(
'🔄 Scan Recovered',
'Your previous scan was automatically recovered after the app restart.',
[
{ text: 'View Result', onPress: () => handleScanResult(scanResult) },
{ text: 'Dismiss', onPress: () => setHasRecoveredResult(false) }
]
);
}
} catch (error) {
console.error('Recovery check failed:', error);
}
};
const startScanning = async () => {
const config: ScannerConfig = {
scannerMode: SCANNER_MODE.FULL,
isGalleryImportRequired: false,
pageLimit: 5
};
try {
const result = await NativeDocScanner.scanDocumentAsync(config);
handleScanResult(result);
} catch (error) {
console.error('Scanning failed:', error);
}
};
const handleScanResult = (result) => {
console.log('Scan successful:', result);
// Process your scan result here
};
return (
<View>
{hasRecoveredResult && (
<View style={{ backgroundColor: '#e3f2fd', padding: 10 }}>
<Text>🔄 Recovered scan result available</Text>
</View>
)}
<TouchableOpacity onPress={startScanning}>
<Text>Start Scanning</Text>
</TouchableOpacity>
</View>
);
};
```
Checks for scan results from interrupted sessions and returns only results from the current session.
```typescript
const recoveryResult = await NativeDocScanner.checkForCrashRecovery();
if (recoveryResult) {
const { scanResult, fromCrashRecovery } = recoveryResult;
// scanResult: JSON string of the scan result
// fromCrashRecovery: Always true when recovery data is available
}
```
Legacy method for backward compatibility. Gets the last scan result regardless of session.
```typescript
const lastResult = await NativeDocScanner.getLastScanResult();
if (lastResult) {
const scanResult = JSON.parse(lastResult.scanResult);
// Process the last scan result
}
```
Manually clear all cached scan data. Useful for testing or cleanup.
```typescript
const cleared = await NativeDocScanner.clearScanCache();
console.log('Cache cleared:', cleared);
```
**Android Scenarios:**
- 📱 App process killed by Android due to memory pressure during ML Kit scanning
- 🔄 App force-closed or restarted during scanning
- ⚡ System-initiated memory cleanup (TRIM_MEMORY_COMPLETE events)
- 🔋 Low memory conditions during document processing
**iOS Scenarios:**
- 📱 App terminated while VisionKit scanner is active
- 🔄 App backgrounded and memory reclaimed during scanning
- ⚡ Manual app termination during document processing
### Important Notes
- ✅ **Session-based**: Only recovers results from the **current interrupted scan session**, not previous completed scans
- ✅ **Automatic cleanup**: Recovery data is automatically cleared after successful delivery
- ✅ **Cross-platform consistency**: Same API and behavior on both iOS and Android
- ✅ **No false positives**: Won't show old results from previous app sessions
- ✅ **Memory efficient**: Uses minimal storage for recovery data
## ⚙️ Configuration Options
### ScannerConfig
| Property | Type | Description | Default |
|----------|------|-------------|---------|
| `scannerMode` | `SCANNER_MODE` | Quality/speed tradeoff | `SCANNER_MODE.FULL` |
| `isGalleryImportRequired` | `boolean` | Allow gallery import (Android only) | `false` |
| `pageLimit` | `number` | Maximum pages to scan (1-50, or -1 for unlimited) | `5` |
| `maxSizeLimit` | `number` | Maximum total file size in bytes (optional) | `104857600` (100MB) |
### Scanner Modes
| Mode | Value | Quality | Speed | Description |
|------|-------|---------|-------|-------------|
| `FULL` | `1` | Highest | Slower | Best quality with advanced processing |
| `BASE_WITH_FILTER` | `2` | Medium | Medium | Good quality with basic filtering |
| `BASE` | `3` | Basic | Fastest | Quick scanning with minimal processing |
### File Size Management
The library includes built-in file size validation to prevent large uploads:
- **Default Limit**: 100MB total size for all scanned images
- **Cross-Platform**: Works on both iOS and Android
- **Real-time Validation**:
- **iOS**: Checks size before processing each page during scanning
- **Android**: Validates total size after scanning completes
- **User Alerts**: Native platform alerts when size limit is exceeded
- **Configurable**: Set custom limits via `maxSizeLimit` parameter
- **Size Reporting**: Returns detailed size information in scan results
- **Error Handling**: Returns `SIZE_LIMIT_EXCEEDED` error when limit exceeded
```typescript
// Configure size limit (50MB example)
const config: ScannerConfig = {
scannerMode: SCANNER_MODE.FULL,
pageLimit: 10,
maxSizeLimit: 50 * 1024 * 1024 // 50MB limit
};
// Check sizes in result
NativeDocScanner.scanDocumentAsync(config)
.then(result => {
console.log(`Total images: ${result.totalImageSize} bytes`);
console.log(`PDF size: ${result.pdfSize} bytes`);
console.log('Individual sizes:', result.imageSizes);
})
.catch(error => {
if (error.message === 'SIZE_LIMIT_EXCEEDED') {
console.log('Scanning stopped due to size limit');
// Handle size limit exceeded
// iOS: User was alerted during scanning
// Android: User was alerted after scanning
}
});
```
Scan documents using callback-based API.
**Parameters:**
- `config: ScannerConfig` - Scanner configuration
- `onSuccess: (result: ScannerResult) => void` - Success callback
- `onError: (error: string) => void` - Error callback
Scan documents using Promise-based API.
**Parameters:**
- `config: ScannerConfig` - Scanner configuration
**Returns:** Promise resolving to `ScannerResult`
Get scanner capabilities and platform information.
**Returns:** Scanner capabilities object or null if not initialized
Check if the native module is ready for use.
**Returns:** Boolean indicating readiness
Check for scan results from interrupted sessions (crash recovery). Only returns results from the current interrupted scan session.
**Returns:** Promise resolving to crash recovery data or null if no pending results
#### `getLastScanResult(): Promise<LastScanResult | null>`
Get the last scan result (legacy method for backward compatibility).
**Returns:** Promise resolving to last scan result or null
#### `clearScanCache(): Promise<boolean>`
Clear all cached scan data. Useful for testing or manual cleanup.
**Returns:** Promise resolving to true when cleared successfully
### Types
#### ScannerResult
```typescript
interface ScannerResult {
imagePaths: {[key: string]: string}; // Paths to scanned images
isPdfAvailable: boolean; // Whether PDF was generated
PdfUri: string; // Path to generated PDF
PdfPageCount: number; // Number of pages in PDF
totalImageSize: number; // Total size of all images in bytes
pdfSize: number; // Size of generated PDF in bytes
imageSizes: {[key: string]: number}; // Individual image sizes in bytes
}
```
```typescript
interface ScannerCapabilities {
isTurboModuleEnabled: boolean; // Architecture being used
features: {
galleryImport: boolean; // Gallery import support
pdfGeneration: boolean; // PDF generation capability
multiPageScan: boolean; // Multi-page scanning
imageFiltering: boolean; // Image filtering support
};
platform: 'ios' | 'android'; // Current platform
framework: 'VisionKit' | 'MLKit' | 'Unknown'; // Native framework
}
```
```typescript
interface CrashRecoveryResult {
scanResult: string; // Scan result data recovered from interrupted session
fromCrashRecovery: boolean; // Whether this result came from crash recovery (always true)
}
```
```typescript
interface LastScanResult {
scanResult: string; // Scan result data (JSON string)
}
```
This library uses **automatic architecture detection** to provide optimal performance:
- **New Architecture (TurboModules)** - When available, automatically uses TurboModules for better performance
- **Legacy Bridge** - Falls back to traditional React Native bridge for maximum compatibility
- **Same API** - Your code remains identical regardless of architecture
### Platform Implementation
| Platform | Framework | Features |
|----------|-----------|----------|
| **iOS** | VisionKit | Document camera, automatic edge detection, multi-page, real-time size validation |
| **Android** | ML Kit | Document scanner, gallery import, image processing, post-scan size validation |
## 📱 Platform Support
| React Native | iOS | Android | New Architecture |
|--------------|-----|---------|------------------|
| 0.68+ | ✅ 15.1+ | ✅ API 21+ | ✅ Supported |
| 0.70+ | ✅ 15.1+ | ✅ API 21+ | ✅ Optimized |
## 🔧 Troubleshooting
### Common Issues
#### "Native module not found"
- Ensure the library is properly installed: `npm install react-native-native-doc-scanner`
- For React Native < 0.60, manually link the library
- Clean and rebuild your project
#### "Activity class does not exist" (Android)
- Make sure you've added the ScannerActivity to your AndroidManifest.xml
- Ensure the activity declaration is inside the `<application>` tag
- Clean and rebuild your Android project: `cd android && ./gradlew clean`
#### iOS Build Issues
- Make sure iOS deployment target is 15.1 or higher
- Verify VisionKit framework is available on your target devices
- Check camera permissions in Info.plist
#### Android Build Issues
- Ensure minSdkVersion is 21 or higher
- Verify Google Play Services is available
- Check camera and storage permissions
### Debug Information
Check if the library is working correctly:
```typescript
import NativeDocScanner from 'react-native-native-doc-scanner';
console.log('Scanner ready:', NativeDocScanner.isReady());
console.log('Capabilities:', NativeDocScanner.getCapabilities());
```
MIT © [rahulunni73](https://github.com/rahulunni73)
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.
- 🐛 **Bug Reports**: [GitHub Issues](https://github.com/rahulunni73/react-native-native-doc-scanner/issues)
- 💡 **Feature Requests**: [GitHub Discussions](https://github.com/rahulunni73/react-native-native-doc-scanner/discussions)
- 📧 **Email**: rahulunni73@gmail.com
---
**Made with ❤️ for the React Native community**
[![npm version](https: