([]);
26 |
27 | useEffect(() => {
28 | checkCameraPermission();
29 | return () => {
30 | // Cleanup on unmount
31 | if (isScanning) {
32 | BarcodeScanner.stopScanning();
33 | }
34 | };
35 | // eslint-disable-next-line react-hooks/exhaustive-deps
36 | }, []);
37 |
38 | const handleBarcodeScanned = (results: BarcodeResult[]) => {
39 | console.log('Barcode / QR Code scanned:', results);
40 | if (results.length > 0) {
41 | setScannedData(results[0] ?? null);
42 | setScanHistory((prev) => [...results, ...prev].slice(0, 20)); // Keep last 20 scans
43 | }
44 | };
45 |
46 | const startScanning = async () => {
47 | try {
48 | await BarcodeScanner.startScanning(handleBarcodeScanned);
49 | setIsScanning(true);
50 | console.log('Scanning started');
51 | } catch (error: any) {
52 | console.error('Error starting scanner:', error);
53 | Alert.alert('Error', error.message || 'Failed to start scanning');
54 | }
55 | };
56 |
57 | const stopScanning = async () => {
58 | try {
59 | await BarcodeScanner.stopScanning();
60 | setIsScanning(false);
61 | console.log('Scanning stopped');
62 | } catch (error: any) {
63 | console.error('Error stopping scanner:', error);
64 | }
65 | };
66 |
67 | const toggleFlashlight = async () => {
68 | try {
69 | if (flashEnabled) {
70 | await BarcodeScanner.disableFlashlight();
71 | setFlashEnabled(false);
72 | console.log('Flashlight disabled');
73 | } else {
74 | await BarcodeScanner.enableFlashlight();
75 | setFlashEnabled(true);
76 | console.log('Flashlight enabled');
77 | }
78 | } catch (error: any) {
79 | console.error('Error toggling flashlight:', error);
80 | Alert.alert('Error', 'Could not toggle flashlight');
81 | }
82 | };
83 |
84 | const releaseCamera = async () => {
85 | try {
86 | await BarcodeScanner.releaseCamera();
87 | setIsScanning(false);
88 | setFlashEnabled(false);
89 | console.log('Camera released');
90 | } catch (error: any) {
91 | console.error('Error releasing camera:', error);
92 | }
93 | };
94 |
95 | const clearHistory = () => {
96 | setScanHistory([]);
97 | setScannedData(null);
98 | };
99 |
100 | const checkCameraPermission = async () => {
101 | try {
102 | // Check if permission is already granted
103 | const hasPermission = await BarcodeScanner.hasCameraPermission();
104 |
105 | if (hasPermission) {
106 | setIsCameraPermissionGranted(true);
107 | return;
108 | }
109 |
110 | // Request permission if not granted
111 | const granted = await BarcodeScanner.requestCameraPermission();
112 |
113 | if (granted) {
114 | setIsCameraPermissionGranted(true);
115 | Alert.alert('Success', 'Camera permission granted!');
116 | } else {
117 | setIsCameraPermissionGranted(false);
118 | Alert.alert(
119 | 'Permission Denied',
120 | 'Camera permission is required to scan barcodes and QR codes.',
121 | [
122 | { text: 'Cancel', style: 'cancel' },
123 | { text: 'Try Again', onPress: checkCameraPermission },
124 | ]
125 | );
126 | }
127 | } catch (error: any) {
128 | console.error('Error checking camera permission:', error);
129 | Alert.alert('Error', 'Failed to check camera permission');
130 | }
131 | };
132 |
133 | if (!isCameraPermissionGranted) {
134 | return (
135 |
136 |
137 |
138 | 📷 Camera Permission Required
139 |
140 |
141 | This app needs camera access to scan barcodes and QR codes.
142 |
143 |
148 |
149 |
150 | );
151 | }
152 |
153 | return (
154 |
155 |
156 | Barcode Scanner
157 |
158 | {isScanning ? '📸 Scanning...' : '⏸️ Ready to scan'}
159 |
160 |
161 |
162 | {/* Camera Preview */}
163 | {isScanning && (
164 |
165 |
166 |
167 |
168 |
169 |
170 | )}
171 |
172 |
173 |
177 |
178 | {isScanning ? '⏹ Stop Scanning' : '▶️ Start Scanning'}
179 |
180 |
181 |
182 | {isScanning && (
183 |
187 |
188 | {flashEnabled ? '🔦 Flash ON' : '🔦 Flash OFF'}
189 |
190 |
191 | )}
192 |
193 |
197 | 🔓 Release Camera
198 |
199 |
200 |
201 | {scannedData && (
202 |
203 |
204 | ✅ Last Scanned
205 |
206 | Clear
207 |
208 |
209 |
210 |
211 | Type:
212 | {scannedData.type}
213 |
214 |
215 | Data:
216 |
217 | {scannedData.data}
218 |
219 |
220 | {scannedData.bounds && (
221 |
222 | Size:
223 |
224 | {scannedData.bounds.width.toFixed(0)} x{' '}
225 | {scannedData.bounds.height.toFixed(0)}
226 |
227 |
228 | )}
229 |
230 |
231 | )}
232 |
233 | {scanHistory.length > 0 && (
234 |
235 | 📋 Scan History
236 |
237 | {scanHistory.map((item, index) => (
238 |
239 | {item.type}
240 |
241 | {item.data}
242 |
243 |
244 | ))}
245 |
246 |
247 | )}
248 |
249 |
250 |
251 | 💡 Point camera at barcode or QR code
252 |
253 |
254 |
255 | );
256 | }
257 |
258 | const styles = StyleSheet.create({
259 | container: {
260 | flex: 1,
261 | backgroundColor: '#f5f5f5',
262 | },
263 | // Camera Preview
264 | cameraContainer: {
265 | height: 400,
266 | backgroundColor: '#000',
267 | margin: 16,
268 | marginTop: 0,
269 | borderRadius: 12,
270 | overflow: 'hidden',
271 | position: 'relative',
272 | },
273 | camera: {
274 | flex: 1,
275 | },
276 | scannerOverlay: {
277 | ...StyleSheet.absoluteFillObject,
278 | justifyContent: 'center',
279 | alignItems: 'center',
280 | },
281 | scannerFrame: {
282 | width: 250,
283 | height: 250,
284 | borderWidth: 2,
285 | borderColor: '#00FF00',
286 | borderRadius: 12,
287 | backgroundColor: 'transparent',
288 | },
289 | // Permission Screen
290 | permissionContainer: {
291 | flex: 1,
292 | justifyContent: 'center',
293 | alignItems: 'center',
294 | padding: 20,
295 | },
296 | permissionText: {
297 | fontSize: 24,
298 | fontWeight: 'bold',
299 | marginBottom: 12,
300 | textAlign: 'center',
301 | },
302 | permissionSubtext: {
303 | fontSize: 16,
304 | color: '#666',
305 | marginBottom: 24,
306 | textAlign: 'center',
307 | },
308 | // Header
309 | header: {
310 | backgroundColor: '#007AFF',
311 | padding: 20,
312 | paddingTop: Platform.OS === 'ios' ? 10 : 20,
313 | },
314 | headerTitle: {
315 | fontSize: 24,
316 | fontWeight: 'bold',
317 | color: 'white',
318 | textAlign: 'center',
319 | },
320 | headerSubtitle: {
321 | fontSize: 16,
322 | color: 'white',
323 | textAlign: 'center',
324 | marginTop: 8,
325 | opacity: 0.9,
326 | },
327 | // Controls
328 | controls: {
329 | padding: 16,
330 | gap: 12,
331 | },
332 | button: {
333 | backgroundColor: '#007AFF',
334 | padding: 16,
335 | borderRadius: 12,
336 | alignItems: 'center',
337 | },
338 | buttonActive: {
339 | backgroundColor: '#34C759',
340 | },
341 | buttonText: {
342 | color: 'white',
343 | fontSize: 16,
344 | fontWeight: '600',
345 | },
346 | buttonSecondary: {
347 | backgroundColor: '#FF3B30',
348 | padding: 16,
349 | borderRadius: 12,
350 | alignItems: 'center',
351 | },
352 | buttonSecondaryText: {
353 | color: 'white',
354 | fontSize: 16,
355 | fontWeight: '600',
356 | },
357 | // Result
358 | result: {
359 | margin: 16,
360 | padding: 16,
361 | backgroundColor: 'white',
362 | borderRadius: 12,
363 | shadowColor: '#000',
364 | shadowOffset: { width: 0, height: 2 },
365 | shadowOpacity: 0.1,
366 | shadowRadius: 4,
367 | elevation: 3,
368 | },
369 | resultHeader: {
370 | flexDirection: 'row',
371 | justifyContent: 'space-between',
372 | alignItems: 'center',
373 | marginBottom: 12,
374 | },
375 | resultTitle: {
376 | fontSize: 18,
377 | fontWeight: 'bold',
378 | color: '#333',
379 | },
380 | clearButton: {
381 | color: '#007AFF',
382 | fontSize: 14,
383 | fontWeight: '600',
384 | },
385 | resultContent: {
386 | gap: 8,
387 | },
388 | resultRow: {
389 | flexDirection: 'row',
390 | paddingVertical: 8,
391 | borderBottomWidth: 1,
392 | borderBottomColor: '#f0f0f0',
393 | },
394 | resultLabel: {
395 | fontSize: 14,
396 | fontWeight: '600',
397 | color: '#666',
398 | width: 60,
399 | },
400 | resultValue: {
401 | fontSize: 14,
402 | color: '#333',
403 | flex: 1,
404 | fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
405 | },
406 | resultText: {
407 | fontSize: 16,
408 | marginVertical: 4,
409 | },
410 | // History
411 | history: {
412 | margin: 16,
413 | marginTop: 0,
414 | backgroundColor: 'white',
415 | borderRadius: 12,
416 | padding: 16,
417 | shadowColor: '#000',
418 | shadowOffset: { width: 0, height: 2 },
419 | shadowOpacity: 0.1,
420 | shadowRadius: 4,
421 | elevation: 3,
422 | maxHeight: 200,
423 | },
424 | historyTitle: {
425 | fontSize: 16,
426 | fontWeight: 'bold',
427 | marginBottom: 12,
428 | color: '#333',
429 | },
430 | historyList: {
431 | maxHeight: 150,
432 | },
433 | historyItem: {
434 | flexDirection: 'row',
435 | justifyContent: 'space-between',
436 | paddingVertical: 8,
437 | borderBottomWidth: 1,
438 | borderBottomColor: '#f0f0f0',
439 | },
440 | historyType: {
441 | fontSize: 12,
442 | fontWeight: '600',
443 | color: '#007AFF',
444 | width: 100,
445 | },
446 | historyData: {
447 | fontSize: 12,
448 | color: '#666',
449 | flex: 1,
450 | fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
451 | },
452 | // Footer
453 | footer: {
454 | padding: 16,
455 | alignItems: 'center',
456 | },
457 | footerText: {
458 | fontSize: 14,
459 | color: '#666',
460 | textAlign: 'center',
461 | },
462 | });
463 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @pushpendersingh/react-native-scanner
2 |
3 |
4 |
5 | 
6 | 
7 | 
8 | 
9 |
10 | **A powerful, easy-to-use QR code & Barcode Scanner for React Native with New Architecture support**
11 |
12 |
13 |
14 | ---
15 |
16 | ## 📖 About
17 |
18 | A QR code & Barcode Scanner for React Native Projects.
19 |
20 | For React Native developers that need to scan barcodes and QR codes in their apps, this package is a useful resource. It supports React Native's new Fabric Native architecture and was created in Kotlin (Android) and Swift (iOS) with Objective-C++ bridges.
21 |
22 | With this package, users can quickly and easily scan barcodes and QR codes with their device's camera. Using this package, several types of codes can be scanned, and it is simple to use and integrate into your existing projects.
23 |
24 | If you want to provide your React Native app the ability to read barcodes and QR codes, you should definitely give this package some thought.
25 |
26 | The `@pushpendersingh/react-native-scanner` package also includes a flashlight feature that can be turned on and off. This can be useful when scanning QR codes & barcodes in low light conditions.
27 |
28 | ---
29 |
30 | ## ✨ Features
31 |
32 | - 📱 **Cross-platform** - Works on both iOS and Android
33 | - 🚀 **New Architecture Ready** - Built with Turbo Modules & Fabric
34 | - 📷 **Camera Preview** - Real-time camera feed with preview
35 | - 🔍 **Multiple Formats** - Supports 13+ barcode formats (QR, EAN, Code128, etc.)
36 | - ⚡ **High Performance** - Optimized with CameraX (Android) & AVFoundation (iOS)
37 | - 🎯 **Easy Integration** - Simple API with event-based scanning
38 | - 💡 **Flash Support** - Toggle flashlight on/off
39 | - 🔄 **Lifecycle Management** - Automatic camera resource handling
40 | - 🔒 **Thread-Safe** - Modern concurrency patterns (Swift Actors & Kotlin synchronization)
41 | - 🎨 **Customizable** - Flexible styling options
42 |
43 | ---
44 |
45 | ## 📦 Installation
46 |
47 | ```bash
48 | npm install @pushpendersingh/react-native-scanner
49 | ```
50 |
51 | or
52 |
53 | ```bash
54 | yarn add @pushpendersingh/react-native-scanner
55 | ```
56 |
57 | ### iOS Setup
58 |
59 | 1. Install CocoaPods dependencies:
60 |
61 | ```bash
62 | cd ios && pod install && cd ..
63 | ```
64 |
65 | 2. Add camera permission to `Info.plist`:
66 |
67 | ```xml
68 | NSCameraUsageDescription
69 | We need camera access to scan barcodes
70 | ```
71 |
72 | ### Android Setup
73 |
74 | Add camera permission to `AndroidManifest.xml`:
75 |
76 | ```xml
77 |
78 |
79 | ```
80 |
81 | ---
82 |
83 | ## 🎯 Usage
84 |
85 | ### Basic Example
86 |
87 | ```tsx
88 | import React, { useState } from 'react';
89 | import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
90 | import { BarcodeScanner, CameraView } from '@pushpendersingh/react-native-scanner';
91 |
92 | export default function App() {
93 | const [scanning, setScanning] = useState(false);
94 | const [result, setResult] = useState('');
95 |
96 | const startScanning = async () => {
97 | try {
98 | setScanning(true);
99 | await BarcodeScanner.startScanning((barcodes) => {
100 | console.log('Barcodes detected:', barcodes);
101 | if (barcodes.length > 0) {
102 | const barcode = barcodes[0];
103 | setResult(`${barcode.type}: ${barcode.data}`);
104 | }
105 | stopScanning();
106 | });
107 | } catch (error) {
108 | console.error('Failed to start scanning:', error);
109 | }
110 | };
111 |
112 | const stopScanning = async () => {
113 | try {
114 | await BarcodeScanner.stopScanning();
115 | setScanning(false);
116 | } catch (error) {
117 | console.error('Failed to stop scanning:', error);
118 | }
119 | };
120 |
121 | return (
122 |
123 |
124 |
125 |
126 |
130 |
131 | {scanning ? 'Stop Scanning' : 'Start Scanning'}
132 |
133 |
134 |
135 | {result && (
136 |
137 | {result}
138 |
139 | )}
140 |
141 |
142 | );
143 | }
144 |
145 | const styles = StyleSheet.create({
146 | container: {
147 | flex: 1,
148 | },
149 | camera: {
150 | flex: 1,
151 | },
152 | controls: {
153 | position: 'absolute',
154 | bottom: 50,
155 | left: 0,
156 | right: 0,
157 | alignItems: 'center',
158 | },
159 | button: {
160 | backgroundColor: '#007AFF',
161 | paddingHorizontal: 32,
162 | paddingVertical: 16,
163 | borderRadius: 8,
164 | },
165 | buttonText: {
166 | color: 'white',
167 | fontSize: 16,
168 | fontWeight: 'bold',
169 | },
170 | resultContainer: {
171 | marginTop: 16,
172 | backgroundColor: 'rgba(0,0,0,0.7)',
173 | padding: 16,
174 | borderRadius: 8,
175 | },
176 | resultText: {
177 | color: 'white',
178 | fontSize: 14,
179 | },
180 | });
181 | ```
182 |
183 | ---
184 |
185 | ## 📚 API Reference
186 |
187 | ### `BarcodeScanner`
188 |
189 | #### `startScanning(callback)`
190 |
191 | Starts the barcode scanning process.
192 |
193 | ```typescript
194 | BarcodeScanner.startScanning((barcodes: BarcodeResult[]) => {
195 | barcodes.forEach((barcode) => {
196 | console.log('Type:', barcode.type);
197 | console.log('Data:', barcode.data);
198 | console.log('Raw:', barcode.raw);
199 | });
200 | });
201 | ```
202 |
203 | **Parameters:**
204 | - `callback: (barcodes: BarcodeResult[]) => void` - Called when barcodes are detected
205 |
206 | **Returns:** `Promise`
207 |
208 | ---
209 |
210 | #### `stopScanning()`
211 |
212 | Stops the barcode scanning process.
213 |
214 | ```typescript
215 | await BarcodeScanner.stopScanning();
216 | ```
217 |
218 | **Returns:** `Promise`
219 |
220 | ---
221 |
222 | #### `releaseCamera()`
223 |
224 | Releases the camera resources completely.
225 |
226 | ```typescript
227 | await BarcodeScanner.releaseCamera();
228 | ```
229 |
230 | **Returns:** `Promise`
231 |
232 | ---
233 |
234 | #### `enableFlashlight()` / `disableFlashlight()`
235 |
236 | Control the camera flashlight (torch) explicitly.
237 |
238 | ```typescript
239 | // Turn flashlight on
240 | await BarcodeScanner.enableFlashlight();
241 |
242 | // Turn flashlight off
243 | await BarcodeScanner.disableFlashlight();
244 | ```
245 |
246 | **Returns:** `Promise`
247 |
248 | ---
249 |
250 | #### `hasCameraPermission()`
251 |
252 | Checks if camera permission is currently granted.
253 |
254 | ```typescript
255 | const hasPermission = await BarcodeScanner.hasCameraPermission();
256 | console.log('Camera permission granted:', hasPermission);
257 | ```
258 |
259 | **Returns:** `Promise` - `true` if permission granted, `false` otherwise
260 |
261 | ---
262 |
263 | #### `requestCameraPermission()`
264 |
265 | Requests camera permission from the user with native promise resolution.
266 |
267 | ```typescript
268 | const granted = await BarcodeScanner.requestCameraPermission();
269 | if (granted) {
270 | console.log('Permission granted!');
271 | // Start scanning
272 | } else {
273 | console.log('Permission denied');
274 | // Show error or guide user to settings
275 | }
276 | ```
277 |
278 | **Returns:** `Promise` - `true` if user grants permission, `false` if denied
279 |
280 | **Platform Support:**
281 | - ✅ **iOS**: Fully supported with native callback
282 | - ✅ **Android**: Fully supported with native callback (API 23+)
283 |
284 | **Note:** This method shows the native system permission dialog and waits for the user's response, then resolves the promise based on their choice.
285 |
286 | ---
287 |
288 | ### `CameraView`
289 |
290 | React component that renders the camera preview.
291 |
292 | ```typescript
293 |
294 | ```
295 |
296 | **Props:**
297 | - `style?: ViewStyle` - Style for the camera view container
298 |
299 | ---
300 |
301 | ### Types
302 |
303 | #### `BarcodeResult`
304 |
305 | ```typescript
306 | interface BarcodeResult {
307 | type: string; // Barcode format (e.g., 'QR_CODE', 'EAN_13')
308 | data: string; // Decoded barcode data
309 | raw: string; // Raw barcode value
310 | }
311 | ```
312 |
313 | ---
314 |
315 | ## 🎨 Advanced Usage
316 |
317 | ### Flashlight Control
318 |
319 | ```tsx
320 | import { BarcodeScanner } from '@pushpendersingh/react-native-scanner';
321 |
322 | const [flashEnabled, setFlashEnabled] = useState(false);
323 |
324 | const toggleFlash = async () => {
325 | try {
326 | if (flashEnabled) {
327 | await BarcodeScanner.disableFlashlight();
328 | setFlashEnabled(false);
329 | } else {
330 | await BarcodeScanner.enableFlashlight();
331 | setFlashEnabled(true);
332 | }
333 | } catch (err) {
334 | console.error('Failed to toggle flashlight', err);
335 | }
336 | };
337 | ```
338 |
339 | ### Lifecycle Management
340 |
341 | ```tsx
342 | import { useEffect } from 'react';
343 | import { BarcodeScanner } from '@pushpendersingh/react-native-scanner';
344 |
345 | useEffect(() => {
346 | // Start scanning on mount
347 | BarcodeScanner.startScanning(handleBarcode);
348 |
349 | // Cleanup on unmount
350 | return () => {
351 | BarcodeScanner.stopScanning();
352 | BarcodeScanner.releaseCamera();
353 | };
354 | }, []);
355 | ```
356 |
357 | ### Permission Handling
358 |
359 | The library now provides **built-in native camera permission methods** that work seamlessly on both iOS and Android with proper promise resolution based on user response.
360 |
361 | #### ✅ Using Built-in Permission Methods (Recommended)
362 |
363 | The library includes native methods that handle camera permissions with proper callbacks:
364 |
365 | ```tsx
366 | import React, { useEffect, useState } from 'react';
367 | import { View, Text, Button, Alert } from 'react-native';
368 | import { BarcodeScanner, CameraView } from '@pushpendersingh/react-native-scanner';
369 |
370 | export default function App() {
371 | const [hasPermission, setHasPermission] = useState(null);
372 | const [scanning, setScanning] = useState(false);
373 |
374 | useEffect(() => {
375 | checkPermission();
376 | }, []);
377 |
378 | const checkPermission = async () => {
379 | const granted = await BarcodeScanner.hasCameraPermission();
380 | setHasPermission(granted);
381 | };
382 |
383 | const requestPermission = async () => {
384 | const granted = await BarcodeScanner.requestCameraPermission();
385 | setHasPermission(granted);
386 |
387 | if (granted) {
388 | Alert.alert('Success', 'Camera permission granted!');
389 | } else {
390 | Alert.alert(
391 | 'Permission Denied',
392 | 'Camera permission is required to scan barcodes'
393 | );
394 | }
395 | };
396 |
397 | const startScanning = async () => {
398 | // Check permission before scanning
399 | const granted = await BarcodeScanner.hasCameraPermission();
400 |
401 | if (!granted) {
402 | Alert.alert(
403 | 'Permission Required',
404 | 'Please grant camera permission to scan barcodes',
405 | [{ text: 'Grant Permission', onPress: requestPermission }]
406 | );
407 | return;
408 | }
409 | await BarcodeScanner.startScanning((barcodes) => {
410 | console.log('Scanned:', barcodes);
411 | BarcodeScanner.stopScanning();
412 | setScanning(false);
413 | });
414 | };
415 |
416 | if (hasPermission === null) {
417 | return Checking camera permission... ;
418 | }
419 |
420 | if (hasPermission === false) {
421 | return (
422 |
423 | Camera permission not granted
424 |
425 |
426 | );
427 | }
428 |
429 | return (
430 |
431 |
432 |
436 |
437 | );
438 | }
439 | ```
440 |
441 | **Key Features:**
442 | - ✅ **Cross-platform**: Works on both iOS (API 10+) and Android (API 23+)
443 | - ✅ **Promise-based**: Returns `true` when granted, `false` when denied
444 | - ✅ **Native callbacks**: Waits for actual user response from system dialog
445 | - ✅ **No dependencies**: No need for additional permission libraries
446 |
447 | #### Alternative: Using react-native-permissions
448 |
449 | For more advanced permission handling (checking settings, handling blocked state, etc.), you can use [`react-native-permissions`](https://github.com/zoontek/react-native-permissions):
450 |
451 | ```bash
452 | npm install react-native-permissions
453 | # or
454 | yarn add react-native-permissions
455 | ```
456 |
457 | ```tsx
458 | import { request, check, PERMISSIONS, RESULTS } from 'react-native-permissions';
459 | import { Platform, Linking } from 'react-native';
460 |
461 | const checkCameraPermission = async () => {
462 | const permission = Platform.select({
463 | ios: PERMISSIONS.IOS.CAMERA,
464 | android: PERMISSIONS.ANDROID.CAMERA,
465 | });
466 |
467 | const result = await check(permission);
468 |
469 | switch (result) {
470 | case RESULTS.GRANTED:
471 | return 'granted';
472 | case RESULTS.DENIED:
473 | return 'denied';
474 | case RESULTS.BLOCKED:
475 | return 'blocked';
476 | default:
477 | return 'unavailable';
478 | }
479 | };
480 |
481 | const requestCameraPermission = async () => {
482 | const permission = Platform.select({
483 | ios: PERMISSIONS.IOS.CAMERA,
484 | android: PERMISSIONS.ANDROID.CAMERA,
485 | });
486 |
487 | const result = await request(permission);
488 |
489 | if (result === RESULTS.BLOCKED) {
490 | // User has blocked permission, guide them to settings
491 | Alert.alert(
492 | 'Permission Blocked',
493 | 'Please enable camera permission in settings',
494 | [
495 | { text: 'Cancel', style: 'cancel' },
496 | { text: 'Open Settings', onPress: () => Linking.openSettings() },
497 | ]
498 | );
499 | return false;
500 | }
501 |
502 | return result === RESULTS.GRANTED;
503 | };
504 | ```
505 | ---
506 |
507 | ## 📋 Supported Barcode Formats
508 |
509 | This library supports a wide range of barcode formats across different categories:
510 |
511 | | 1D Product | 1D Industrial | 2D |
512 | |:----------------------|:--------------|:---------------|
513 | | UPC-A | Code 39 | QR Code |
514 | | UPC-E | Code 93 | Data Matrix |
515 | | EAN-8 | Code 128 | Aztec |
516 | | EAN-13 | Codabar | PDF 417 |
517 | | | ITF | |
518 |
519 | **Format Details:**
520 |
521 | - **1D Product Codes**: Commonly used in retail (UPC-A, UPC-E, EAN-8, EAN-13)
522 | - **1D Industrial Codes**: Used in logistics and inventory (Code 39, Code 93, Code 128, Codabar, ITF)
523 | - **2D Codes**: High-density codes for storing more data (QR Code, Data Matrix, Aztec, PDF 417)
524 |
525 | **Total Supported Formats**: 13 barcode types
526 |
527 | ---
528 |
529 | ## 🛠️ Technical Details
530 |
531 | ### Android
532 |
533 | - **CameraX 1.5.0** - Modern camera API with lifecycle awareness
534 | - **ML Kit Barcode Scanning 17.3.0** - Google's ML-powered barcode detection
535 | - **Kotlin** - Native implementation with thread-safe synchronization
536 | - **Thread Safety** - Uses `@Volatile` and `@Synchronized` for concurrent access protection
537 |
538 | ### iOS
539 |
540 | - **AVFoundation** - Native camera framework
541 | - **Vision Framework** - Apple's barcode detection
542 | - **Swift 5.0+** - Native implementation with modern concurrency
543 | - **Thread Safety** - Uses Swift Actors for isolated state management and thread-safe operations
544 |
545 | ### React Native
546 |
547 | - **New Architecture** - Turbo Modules + Fabric support
548 | - **React Native 0.80+** - Minimum version requirement
549 | - **Codegen** - Automatic native interface generation
550 |
551 | ---
552 |
553 | ## 🔧 Troubleshooting
554 |
555 | ### Camera Preview Not Showing
556 |
557 | **iOS:**
558 | - Check camera permission in `Info.plist`
559 | - Ensure you're running on a physical device (simulator doesn't have camera)
560 |
561 | **Android:**
562 | - Check camera permission in `AndroidManifest.xml`
563 | - Verify Google Play Services is installed
564 |
565 | ### Barcode Not Scanning
566 |
567 | - Ensure good lighting conditions
568 | - Hold barcode steady and at proper distance
569 | - Check that barcode format is supported
570 | - Verify barcode is not damaged or distorted
571 |
572 | **Tips for scanning IMEI:**
573 | - Ensure the IMEI barcode is clean and undamaged
574 | - Use good lighting (enable flashlight if needed)
575 | - Hold device steady at 10-15cm distance from the barcode
576 | - IMEI barcodes are usually found on device packaging or SIM trays
577 |
578 | ### Build Issues
579 |
580 | **iOS:**
581 | ```bash
582 | cd ios && pod deintegrate && pod install && cd ..
583 | ```
584 |
585 | **Android:**
586 | ```bash
587 | cd android && ./gradlew clean && cd ..
588 | ```
589 |
590 | ---
591 |
592 | ## 📖 Example App
593 |
594 | Check out the [example app](./example) for a complete working implementation.
595 |
596 | **Run the example:**
597 |
598 | ```bash
599 | # Install dependencies
600 | cd example && yarn
601 |
602 | # iOS
603 | cd example && npx pod-install && yarn ios
604 |
605 | # Android
606 | cd example && yarn android
607 | ```
608 |
609 | ---
610 |
611 | ## 🚀 Roadmap & Future Improvements
612 |
613 | We're constantly working to improve this library. Here are some planned enhancements:
614 |
615 | ### Planned Features
616 |
617 | - [ ] **Barcode Generation** - Add ability to generate barcodes/QR codes
618 | - [ ] **Image Analysis** - Support scanning barcodes from gallery images
619 | - [ ] **Advanced Camera Controls** - Zoom, focus, and exposure controls
620 |
621 | ---
622 |
623 | ## 🤝 Contributing
624 |
625 | We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
626 |
627 | ### Development Workflow
628 |
629 | 1. Fork the repository
630 | 2. Create a feature branch: `git checkout -b feature/my-feature`
631 | 3. Make your changes
632 | 4. Run tests: `yarn test`
633 | 5. Commit: `git commit -m 'Add new feature'`
634 | 6. Push: `git push origin feature/my-feature`
635 | 7. Open a Pull Request
636 |
637 | Please read our [Code of Conduct](CODE_OF_CONDUCT.md) before contributing.
638 |
639 | ---
640 |
641 | ## 📄 License
642 |
643 | MIT © [Pushpender Singh](https://github.com/pushpender-singh-ap)
644 |
645 | ---
646 |
647 | ## 🙏 Acknowledgments
648 |
649 | - Built with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
650 | - Uses [CameraX](https://developer.android.com/training/camerax) on Android
651 | - Uses [AVFoundation](https://developer.apple.com/av-foundation/) on iOS
652 | - Barcode detection powered by [ML Kit](https://developers.google.com/ml-kit/vision/barcode-scanning) (Android) and [Vision](https://developer.apple.com/documentation/vision) (iOS)
653 |
654 | ---
655 |
656 | ## 📞 Support
657 |
658 | - 🐛 [Report a bug](https://github.com/pushpender-singh-ap/react-native-scanner/issues)
659 | - 💡 [Request a feature](https://github.com/pushpender-singh-ap/react-native-scanner/issues)
660 |
661 | ---
662 |
663 |
664 |
665 | **If you find this package helpful, please give it a ⭐️ on [GitHub](https://github.com/pushpender-singh-ap/react-native-scanner)!**
666 |
667 |
668 |
--------------------------------------------------------------------------------
/android/src/main/java/com/pushpendersingh/reactnativescanner/CameraManager.kt:
--------------------------------------------------------------------------------
1 | package com.pushpendersingh.reactnativescanner
2 |
3 | import android.Manifest
4 | import android.content.Context
5 | import android.content.pm.PackageManager
6 | import android.graphics.Rect
7 | import android.util.Log
8 | import androidx.appcompat.app.AppCompatActivity
9 | import androidx.camera.core.CameraControl
10 | import androidx.camera.core.CameraSelector
11 | import androidx.camera.core.ImageAnalysis
12 | import androidx.camera.core.Preview
13 | import androidx.camera.lifecycle.ProcessCameraProvider
14 | import androidx.camera.view.PreviewView
15 | import androidx.core.content.ContextCompat
16 | import com.facebook.react.bridge.Arguments
17 | import com.facebook.react.bridge.ReactApplicationContext
18 | import com.facebook.react.bridge.WritableArray
19 | import com.facebook.react.bridge.WritableMap
20 | import com.google.mlkit.vision.barcode.BarcodeScanner
21 | import com.google.mlkit.vision.barcode.BarcodeScannerOptions
22 | import com.google.mlkit.vision.barcode.BarcodeScanning
23 | import com.google.mlkit.vision.barcode.common.Barcode
24 | import java.util.concurrent.ExecutorService
25 | import java.util.concurrent.Executors
26 | import java.util.concurrent.TimeUnit
27 | import java.util.concurrent.atomic.AtomicBoolean
28 | import java.util.concurrent.atomic.AtomicReference
29 | import java.util.concurrent.locks.ReentrantLock
30 | import kotlin.concurrent.withLock
31 |
32 | class CameraManager(private val reactContext: ReactApplicationContext) {
33 |
34 | private val TAG = "CameraManager"
35 | @Volatile
36 | private var cameraProvider: ProcessCameraProvider? = null
37 | @Volatile
38 | private var cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
39 | private var scanner: BarcodeScanner? = null
40 | @Volatile
41 | private var cameraControl: CameraControl? = null
42 | private var imageAnalysis: ImageAnalysis? = null
43 | private var preview: Preview? = null
44 | private var previewView: PreviewView? = null
45 |
46 | // AtomicBoolean for lock-free scanning flag
47 | private val isScanning = AtomicBoolean(false)
48 | // AtomicReference for thread-safe callback
49 | private val scanCallbackRef = AtomicReference<((WritableArray) -> Unit)?>(null)
50 |
51 | // Lock for synchronizing camera binding operations
52 | private val cameraBindLock = ReentrantLock()
53 | // Flag to prevent concurrent binding
54 | @Volatile
55 | private var isBinding = false
56 | // Lock for executor lifecycle management
57 | private val executorLock = ReentrantLock()
58 |
59 | companion object {
60 | private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
61 | }
62 |
63 | fun hasCameraPermission(): Boolean {
64 | return REQUIRED_PERMISSIONS.all {
65 | ContextCompat.checkSelfPermission(reactContext, it) == PackageManager.PERMISSION_GRANTED
66 | }
67 | }
68 |
69 | fun bindPreviewView(view: PreviewView) {
70 | this.previewView = view
71 | // Only rebind if scanning AND not already in binding process
72 | if (isScanning.get() && !isBinding) {
73 | // Schedule rebind on main executor to avoid race with initializeCamera callback
74 | ContextCompat.getMainExecutor(reactContext).execute {
75 | bindCameraUseCases()
76 | }
77 | }
78 | }
79 |
80 | /**
81 | * Thread-safe executor lifecycle management
82 | * Ensures the camera executor is available and not shutdown.
83 | * Creates a new executor if the current one has been shutdown.
84 | * Uses dedicated lock to prevent race with releaseCamera()
85 | */
86 | private fun ensureExecutor() {
87 | executorLock.withLock {
88 | if (cameraExecutor.isShutdown) {
89 | cameraExecutor = Executors.newSingleThreadExecutor()
90 | Log.d(TAG, "♻️ Recreated camera executor")
91 | }
92 | }
93 | }
94 |
95 | /**
96 | * Safe executor access with validation
97 | * Returns the executor only if it's not shutdown
98 | */
99 | private fun getExecutorSafely(): ExecutorService? {
100 | return executorLock.withLock {
101 | if (!cameraExecutor.isShutdown) cameraExecutor else null
102 | }
103 | }
104 |
105 | /**
106 | * Lock-free scanning with atomic CAS operation
107 | * Eliminates race condition between check and set
108 | */
109 | fun startScanning(callback: (WritableArray) -> Unit) {
110 | if (!hasCameraPermission()) {
111 | throw SecurityException("Camera permission not granted")
112 | }
113 |
114 | // Atomic compare-and-set: only first caller proceeds
115 | if (!isScanning.compareAndSet(false, true)) {
116 | Log.w(TAG, "Scanning already in progress, updating callback")
117 | scanCallbackRef.set(callback)
118 | return
119 | }
120 |
121 | // At this point, we're guaranteed to be the only thread starting scanning
122 | // Set callback immediately after winning the CAS race
123 | scanCallbackRef.set(callback)
124 |
125 | try {
126 | ensureExecutor()
127 | initializeCamera()
128 | } catch (e: Exception) {
129 | // Reset flag on error
130 | isScanning.set(false)
131 | scanCallbackRef.set(null)
132 | throw e
133 | }
134 | }
135 |
136 | private fun initializeCamera() {
137 | Log.d(TAG, "Initializing camera...")
138 | val cameraProviderFuture = ProcessCameraProvider.getInstance(reactContext)
139 |
140 | // Initialize barcode scanner if not already created
141 | if (scanner == null) {
142 | Log.d(TAG, "Creating barcode scanner...")
143 | val options = BarcodeScannerOptions.Builder()
144 | .setBarcodeFormats(
145 | Barcode.FORMAT_QR_CODE,
146 | Barcode.FORMAT_AZTEC,
147 | Barcode.FORMAT_CODE_128,
148 | Barcode.FORMAT_CODE_39,
149 | Barcode.FORMAT_CODE_93,
150 | Barcode.FORMAT_CODABAR,
151 | Barcode.FORMAT_DATA_MATRIX,
152 | Barcode.FORMAT_EAN_13,
153 | Barcode.FORMAT_EAN_8,
154 | Barcode.FORMAT_ITF,
155 | Barcode.FORMAT_PDF417,
156 | Barcode.FORMAT_UPC_A,
157 | Barcode.FORMAT_UPC_E
158 | )
159 | .build()
160 |
161 | scanner = BarcodeScanning.getClient(options)
162 | }
163 |
164 | cameraProviderFuture.addListener({
165 | try {
166 | cameraProvider = cameraProviderFuture.get()
167 | bindCameraUseCases()
168 | } catch (exc: Exception) {
169 | Log.e(TAG, "Error initializing camera: ${exc.message}", exc)
170 | // Reset state on error
171 | isScanning.set(false)
172 | scanCallbackRef.set(null)
173 | }
174 | }, ContextCompat.getMainExecutor(reactContext))
175 | }
176 |
177 | /**
178 | * Thread-safe camera binding with lock
179 | * Prevents concurrent binding operations that could cause IllegalStateException
180 | */
181 | private fun bindCameraUseCases() {
182 | // Use lock to serialize all binding operations
183 | cameraBindLock.withLock {
184 | // Check and set binding flag atomically within lock
185 | if (isBinding) {
186 | Log.w(TAG, "⚠️ Camera binding already in progress, skipping")
187 | return
188 | }
189 | isBinding = true
190 | }
191 |
192 | try {
193 | Log.d(TAG, "Binding camera use cases...")
194 | val currentActivity = reactContext.currentActivity as? AppCompatActivity
195 | if (currentActivity == null || currentActivity.isDestroyed || currentActivity.isFinishing) {
196 | Log.e(TAG, "❌ Current activity is not available")
197 | isScanning.set(false)
198 | scanCallbackRef.set(null)
199 | return
200 | }
201 |
202 | // Get executor safely - may be null if shutdown in progress
203 | val executor = getExecutorSafely()
204 | if (executor == null) {
205 | Log.e(TAG, "❌ Executor is shutdown, cannot bind camera")
206 | isScanning.set(false)
207 | scanCallbackRef.set(null)
208 | return
209 | }
210 |
211 | // Unbind any existing use cases first
212 | cameraProvider?.unbindAll()
213 | Log.d(TAG, "Unbound previous camera use cases")
214 |
215 | val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
216 |
217 | // Setup preview
218 | preview = Preview.Builder()
219 | .build()
220 | .also {
221 | previewView?.let { view ->
222 | it.setSurfaceProvider(view.surfaceProvider)
223 | }
224 | }
225 |
226 | // Use the safely-obtained executor reference
227 | imageAnalysis = ImageAnalysis.Builder()
228 | .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
229 | .build()
230 | .also { analysis ->
231 | analysis.setAnalyzer(executor) { imageProxy ->
232 | // Check atomic boolean
233 | if (!isScanning.get()) {
234 | imageProxy.close()
235 | return@setAnalyzer
236 | }
237 |
238 | val mediaImage = imageProxy.image
239 | if (mediaImage != null) {
240 | processImage(imageProxy, mediaImage)
241 | } else {
242 | imageProxy.close()
243 | }
244 | }
245 | }
246 |
247 | // Bind use cases to camera - include preview if available
248 | val useCases = mutableListOf(imageAnalysis!!)
249 | preview?.let { useCases.add(it) }
250 |
251 | val camera = cameraProvider?.bindToLifecycle(
252 | currentActivity,
253 | cameraSelector,
254 | *useCases.toTypedArray()
255 | )
256 |
257 | cameraControl = camera?.cameraControl
258 | Log.d(TAG, "✅ Camera successfully bound and scanning started (preview: ${preview != null})")
259 | } catch (exc: Exception) {
260 | Log.e(TAG, "Error binding camera use cases: ${exc.message}", exc)
261 | // Reset state on binding error
262 | isScanning.set(false)
263 | scanCallbackRef.set(null)
264 | } finally {
265 | // Always reset binding flag in finally block
266 | cameraBindLock.withLock {
267 | isBinding = false
268 | }
269 | }
270 | }
271 |
272 | private fun processImage(imageProxy: androidx.camera.core.ImageProxy, mediaImage: android.media.Image) {
273 | val scanner = this.scanner
274 | if (scanner == null) {
275 | imageProxy.close()
276 | return
277 | }
278 |
279 | val image = com.google.mlkit.vision.common.InputImage.fromMediaImage(
280 | mediaImage,
281 | imageProxy.imageInfo.rotationDegrees
282 | )
283 |
284 | scanner.process(image)
285 | .addOnSuccessListener { barcodes ->
286 | if (barcodes.isNotEmpty()) {
287 | val results = Arguments.createArray()
288 | var hasValidBarcode = false
289 |
290 | for (barcode in barcodes) {
291 | if (!barcode.rawValue.isNullOrEmpty()) {
292 | val result = createBarcodeResult(barcode)
293 | results.pushMap(result)
294 | hasValidBarcode = true
295 | }
296 | }
297 |
298 | if (hasValidBarcode) {
299 | val callback = scanCallbackRef.get()
300 | callback?.invoke(results)
301 | }
302 | }
303 | }
304 | .addOnFailureListener { e ->
305 | Log.e(TAG, "Barcode scanning failed: ${e.message}", e)
306 | }
307 | .addOnCompleteListener {
308 | imageProxy.close()
309 | }
310 | }
311 |
312 | private fun createBarcodeResult(barcode: Barcode): WritableMap {
313 | val result = Arguments.createMap()
314 | result.putString("data", barcode.rawValue ?: "")
315 | result.putString("type", getBarcodeTypeName(barcode.format))
316 |
317 | barcode.boundingBox?.let { bounds ->
318 | val boundsMap = Arguments.createMap()
319 | boundsMap.putDouble("width", bounds.width().toDouble())
320 | boundsMap.putDouble("height", bounds.height().toDouble())
321 |
322 | val origin = Arguments.createMap()
323 | origin.putMap("topLeft", createPoint(bounds.left.toDouble(), bounds.top.toDouble()))
324 | origin.putMap("bottomLeft", createPoint(bounds.left.toDouble(), bounds.bottom.toDouble()))
325 | origin.putMap("bottomRight", createPoint(bounds.right.toDouble(), bounds.bottom.toDouble()))
326 | origin.putMap("topRight", createPoint(bounds.right.toDouble(), bounds.top.toDouble()))
327 |
328 | boundsMap.putMap("origin", origin)
329 | result.putMap("bounds", boundsMap)
330 | }
331 |
332 | return result
333 | }
334 |
335 | private fun createPoint(x: Double, y: Double): WritableMap {
336 | val point = Arguments.createMap()
337 | point.putDouble("x", x)
338 | point.putDouble("y", y)
339 | return point
340 | }
341 |
342 | private fun getBarcodeTypeName(format: Int): String {
343 | return when (format) {
344 | Barcode.FORMAT_QR_CODE -> "QR_CODE"
345 | Barcode.FORMAT_AZTEC -> "AZTEC"
346 | Barcode.FORMAT_CODE_128 -> "CODE_128"
347 | Barcode.FORMAT_CODE_39 -> "CODE_39"
348 | Barcode.FORMAT_CODE_93 -> "CODE_93"
349 | Barcode.FORMAT_CODABAR -> "CODABAR"
350 | Barcode.FORMAT_DATA_MATRIX -> "DATA_MATRIX"
351 | Barcode.FORMAT_EAN_13 -> "EAN_13"
352 | Barcode.FORMAT_EAN_8 -> "EAN_8"
353 | Barcode.FORMAT_ITF -> "ITF"
354 | Barcode.FORMAT_PDF417 -> "PDF417"
355 | Barcode.FORMAT_UPC_A -> "UPC_A"
356 | Barcode.FORMAT_UPC_E -> "UPC_E"
357 | else -> "UNKNOWN"
358 | }
359 | }
360 |
361 | /**
362 | * Uses atomic operation to stop scanning
363 | * Thread-safe: Can be called from any thread, camera operations executed on main thread
364 | */
365 | fun stopScanning() {
366 | // Atomic CAS: only proceed if actually scanning
367 | if (!isScanning.compareAndSet(true, false)) {
368 | Log.w(TAG, "Scanning is not in progress")
369 | return
370 | }
371 |
372 | try {
373 | Log.d(TAG, "Stopping scanning...")
374 | scanCallbackRef.set(null)
375 |
376 | // Execute camera operations on main thread to avoid IllegalStateException
377 | ContextCompat.getMainExecutor(reactContext).execute {
378 | try {
379 | // Clear the analyzer to stop processing frames
380 | imageAnalysis?.clearAnalyzer()
381 |
382 | // Unbind all use cases from the camera
383 | cameraProvider?.unbindAll()
384 |
385 | // Clear references but DON'T shutdown executor or scanner
386 | // They will be reused if scanning starts again
387 | cameraControl = null
388 | imageAnalysis = null
389 | preview = null
390 |
391 | Log.d(TAG, "✅ Scanning stopped successfully (executor kept alive for reuse)")
392 | } catch (e: Exception) {
393 | Log.e(TAG, "Error stopping scanning on main thread: ${e.message}", e)
394 | }
395 | }
396 | } catch (e: Exception) {
397 | Log.e(TAG, "Error stopping scanning: ${e.message}", e)
398 | // Ensure flag is still set to false even on error
399 | isScanning.set(false)
400 | throw e
401 | }
402 | }
403 |
404 | fun enableFlashlight() {
405 | try {
406 | cameraControl?.enableTorch(true)
407 | Log.d(TAG, "Flashlight enabled")
408 | } catch (e: Exception) {
409 | Log.e(TAG, "Error enabling flashlight: ${e.message}", e)
410 | throw e
411 | }
412 | }
413 |
414 | fun disableFlashlight() {
415 | try {
416 | cameraControl?.enableTorch(false)
417 | Log.d(TAG, "Flashlight disabled")
418 | } catch (e: Exception) {
419 | Log.e(TAG, "Error disabling flashlight: ${e.message}", e)
420 | throw e
421 | }
422 | }
423 |
424 | fun releaseCamera() {
425 | try {
426 | Log.d(TAG, "Releasing camera resources...")
427 |
428 | // Stop scanning first using atomic operation
429 | if (isScanning.compareAndSet(true, false)) {
430 | scanCallbackRef.set(null)
431 | }
432 |
433 | // Wait for binding to complete WITHOUT holding the lock
434 | // This prevents deadlock when startScanning() is called during release
435 | var attempts = 0
436 | while (isBinding && attempts < 100) { // Max wait: 5 seconds
437 | Log.d(TAG, "⏳ Waiting for camera binding to complete before release...")
438 | Thread.sleep(50)
439 | attempts++
440 | }
441 |
442 | if (isBinding) {
443 | Log.w(TAG, "⚠️ Binding still in progress after 5s, forcing release")
444 | }
445 |
446 | // Now safely unbind with lock (binding should be complete)
447 | cameraBindLock.withLock {
448 | // Double-check binding state and unbind
449 | if (isBinding) {
450 | Log.w(TAG, "⚠️ Binding flag still set, unbinding anyway")
451 | }
452 | cameraProvider?.unbindAll()
453 | }
454 |
455 | // Clear all references
456 | cameraProvider = null
457 | cameraControl = null
458 | imageAnalysis = null
459 | preview = null
460 | previewView = null
461 | scanCallbackRef.set(null)
462 |
463 | // Close the barcode scanner
464 | scanner?.close()
465 | scanner = null
466 |
467 | // Use dedicated executor lock for shutdown
468 | // This prevents race with ensureExecutor() and getExecutorSafely()
469 | executorLock.withLock {
470 | if (!cameraExecutor.isShutdown) {
471 | cameraExecutor.shutdown()
472 | try {
473 | if (!cameraExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
474 | cameraExecutor.shutdownNow()
475 | Log.w(TAG, "⚠️ Executor did not terminate gracefully, forced shutdown")
476 | }
477 | } catch (e: InterruptedException) {
478 | cameraExecutor.shutdownNow()
479 | Thread.currentThread().interrupt()
480 | Log.w(TAG, "⚠️ Executor shutdown interrupted")
481 | }
482 | }
483 | }
484 |
485 | Log.d(TAG, "✅ Camera resources released successfully")
486 | } catch (e: Exception) {
487 | Log.e(TAG, "❌ Error releasing camera: ${e.message}", e)
488 | }
489 | }
490 | }
491 |
--------------------------------------------------------------------------------
/example/ios/ReactNativeScannerExample.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 54;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 0C80B921A6F3F58F76C31292 /* libPods-ReactNativeScannerExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-ReactNativeScannerExample.a */; };
11 | 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
12 | 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
13 | 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
14 | 8ADB15D17BF30B6B520E7B09 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
15 | /* End PBXBuildFile section */
16 |
17 | /* Begin PBXFileReference section */
18 | 13B07F961A680F5B00A75B9A /* ReactNativeScannerExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReactNativeScannerExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
19 | 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ReactNativeScannerExample/Images.xcassets; sourceTree = ""; };
20 | 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ReactNativeScannerExample/Info.plist; sourceTree = ""; };
21 | 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeScannerExample/PrivacyInfo.xcprivacy; sourceTree = ""; };
22 | 3B4392A12AC88292D35C810B /* Pods-ReactNativeScannerExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeScannerExample.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeScannerExample/Pods-ReactNativeScannerExample.debug.xcconfig"; sourceTree = ""; };
23 | 5709B34CF0A7D63546082F79 /* Pods-ReactNativeScannerExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeScannerExample.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeScannerExample/Pods-ReactNativeScannerExample.release.xcconfig"; sourceTree = ""; };
24 | 5DCACB8F33CDC322A6C60F78 /* libPods-ReactNativeScannerExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeScannerExample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
25 | 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = ReactNativeScannerExample/AppDelegate.swift; sourceTree = ""; };
26 | 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ReactNativeScannerExample/LaunchScreen.storyboard; sourceTree = ""; };
27 | ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
28 | /* End PBXFileReference section */
29 |
30 | /* Begin PBXFrameworksBuildPhase section */
31 | 13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
32 | isa = PBXFrameworksBuildPhase;
33 | buildActionMask = 2147483647;
34 | files = (
35 | 0C80B921A6F3F58F76C31292 /* libPods-ReactNativeScannerExample.a in Frameworks */,
36 | );
37 | runOnlyForDeploymentPostprocessing = 0;
38 | };
39 | /* End PBXFrameworksBuildPhase section */
40 |
41 | /* Begin PBXGroup section */
42 | 13B07FAE1A68108700A75B9A /* ReactNativeScannerExample */ = {
43 | isa = PBXGroup;
44 | children = (
45 | 13B07FB51A68108700A75B9A /* Images.xcassets */,
46 | 761780EC2CA45674006654EE /* AppDelegate.swift */,
47 | 13B07FB61A68108700A75B9A /* Info.plist */,
48 | 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
49 | 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */,
50 | );
51 | name = ReactNativeScannerExample;
52 | sourceTree = "";
53 | };
54 | 2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
55 | isa = PBXGroup;
56 | children = (
57 | ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
58 | 5DCACB8F33CDC322A6C60F78 /* libPods-ReactNativeScannerExample.a */,
59 | );
60 | name = Frameworks;
61 | sourceTree = "";
62 | };
63 | 832341AE1AAA6A7D00B99B32 /* Libraries */ = {
64 | isa = PBXGroup;
65 | children = (
66 | );
67 | name = Libraries;
68 | sourceTree = "";
69 | };
70 | 83CBB9F61A601CBA00E9B192 = {
71 | isa = PBXGroup;
72 | children = (
73 | 13B07FAE1A68108700A75B9A /* ReactNativeScannerExample */,
74 | 832341AE1AAA6A7D00B99B32 /* Libraries */,
75 | 83CBBA001A601CBA00E9B192 /* Products */,
76 | 2D16E6871FA4F8E400B85C8A /* Frameworks */,
77 | BBD78D7AC51CEA395F1C20DB /* Pods */,
78 | );
79 | indentWidth = 2;
80 | sourceTree = "";
81 | tabWidth = 2;
82 | usesTabs = 0;
83 | };
84 | 83CBBA001A601CBA00E9B192 /* Products */ = {
85 | isa = PBXGroup;
86 | children = (
87 | 13B07F961A680F5B00A75B9A /* ReactNativeScannerExample.app */,
88 | );
89 | name = Products;
90 | sourceTree = "";
91 | };
92 | BBD78D7AC51CEA395F1C20DB /* Pods */ = {
93 | isa = PBXGroup;
94 | children = (
95 | 3B4392A12AC88292D35C810B /* Pods-ReactNativeScannerExample.debug.xcconfig */,
96 | 5709B34CF0A7D63546082F79 /* Pods-ReactNativeScannerExample.release.xcconfig */,
97 | );
98 | path = Pods;
99 | sourceTree = "";
100 | };
101 | /* End PBXGroup section */
102 |
103 | /* Begin PBXNativeTarget section */
104 | 13B07F861A680F5B00A75B9A /* ReactNativeScannerExample */ = {
105 | isa = PBXNativeTarget;
106 | buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ReactNativeScannerExample" */;
107 | buildPhases = (
108 | C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */,
109 | 13B07F871A680F5B00A75B9A /* Sources */,
110 | 13B07F8C1A680F5B00A75B9A /* Frameworks */,
111 | 13B07F8E1A680F5B00A75B9A /* Resources */,
112 | 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
113 | 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */,
114 | E235C05ADACE081382539298 /* [CP] Copy Pods Resources */,
115 | );
116 | buildRules = (
117 | );
118 | dependencies = (
119 | );
120 | name = ReactNativeScannerExample;
121 | productName = ReactNativeScannerExample;
122 | productReference = 13B07F961A680F5B00A75B9A /* ReactNativeScannerExample.app */;
123 | productType = "com.apple.product-type.application";
124 | };
125 | /* End PBXNativeTarget section */
126 |
127 | /* Begin PBXProject section */
128 | 83CBB9F71A601CBA00E9B192 /* Project object */ = {
129 | isa = PBXProject;
130 | attributes = {
131 | LastUpgradeCheck = 1210;
132 | TargetAttributes = {
133 | 13B07F861A680F5B00A75B9A = {
134 | LastSwiftMigration = 1120;
135 | };
136 | };
137 | };
138 | buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "ReactNativeScannerExample" */;
139 | compatibilityVersion = "Xcode 12.0";
140 | developmentRegion = en;
141 | hasScannedForEncodings = 0;
142 | knownRegions = (
143 | en,
144 | Base,
145 | );
146 | mainGroup = 83CBB9F61A601CBA00E9B192;
147 | productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
148 | projectDirPath = "";
149 | projectRoot = "";
150 | targets = (
151 | 13B07F861A680F5B00A75B9A /* ReactNativeScannerExample */,
152 | );
153 | };
154 | /* End PBXProject section */
155 |
156 | /* Begin PBXResourcesBuildPhase section */
157 | 13B07F8E1A680F5B00A75B9A /* Resources */ = {
158 | isa = PBXResourcesBuildPhase;
159 | buildActionMask = 2147483647;
160 | files = (
161 | 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
162 | 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
163 | 8ADB15D17BF30B6B520E7B09 /* PrivacyInfo.xcprivacy in Resources */,
164 | );
165 | runOnlyForDeploymentPostprocessing = 0;
166 | };
167 | /* End PBXResourcesBuildPhase section */
168 |
169 | /* Begin PBXShellScriptBuildPhase section */
170 | 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
171 | isa = PBXShellScriptBuildPhase;
172 | buildActionMask = 2147483647;
173 | files = (
174 | );
175 | inputPaths = (
176 | "$(SRCROOT)/.xcode.env.local",
177 | "$(SRCROOT)/.xcode.env",
178 | );
179 | name = "Bundle React Native code and images";
180 | outputPaths = (
181 | );
182 | runOnlyForDeploymentPostprocessing = 0;
183 | shellPath = /bin/sh;
184 | shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
185 | };
186 | 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = {
187 | isa = PBXShellScriptBuildPhase;
188 | buildActionMask = 2147483647;
189 | files = (
190 | );
191 | inputFileListPaths = (
192 | "${PODS_ROOT}/Target Support Files/Pods-ReactNativeScannerExample/Pods-ReactNativeScannerExample-frameworks-${CONFIGURATION}-input-files.xcfilelist",
193 | );
194 | name = "[CP] Embed Pods Frameworks";
195 | outputFileListPaths = (
196 | "${PODS_ROOT}/Target Support Files/Pods-ReactNativeScannerExample/Pods-ReactNativeScannerExample-frameworks-${CONFIGURATION}-output-files.xcfilelist",
197 | );
198 | runOnlyForDeploymentPostprocessing = 0;
199 | shellPath = /bin/sh;
200 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeScannerExample/Pods-ReactNativeScannerExample-frameworks.sh\"\n";
201 | showEnvVarsInLog = 0;
202 | };
203 | C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = {
204 | isa = PBXShellScriptBuildPhase;
205 | buildActionMask = 2147483647;
206 | files = (
207 | );
208 | inputFileListPaths = (
209 | );
210 | inputPaths = (
211 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
212 | "${PODS_ROOT}/Manifest.lock",
213 | );
214 | name = "[CP] Check Pods Manifest.lock";
215 | outputFileListPaths = (
216 | );
217 | outputPaths = (
218 | "$(DERIVED_FILE_DIR)/Pods-ReactNativeScannerExample-checkManifestLockResult.txt",
219 | );
220 | runOnlyForDeploymentPostprocessing = 0;
221 | shellPath = /bin/sh;
222 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
223 | showEnvVarsInLog = 0;
224 | };
225 | E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = {
226 | isa = PBXShellScriptBuildPhase;
227 | buildActionMask = 2147483647;
228 | files = (
229 | );
230 | inputFileListPaths = (
231 | "${PODS_ROOT}/Target Support Files/Pods-ReactNativeScannerExample/Pods-ReactNativeScannerExample-resources-${CONFIGURATION}-input-files.xcfilelist",
232 | );
233 | name = "[CP] Copy Pods Resources";
234 | outputFileListPaths = (
235 | "${PODS_ROOT}/Target Support Files/Pods-ReactNativeScannerExample/Pods-ReactNativeScannerExample-resources-${CONFIGURATION}-output-files.xcfilelist",
236 | );
237 | runOnlyForDeploymentPostprocessing = 0;
238 | shellPath = /bin/sh;
239 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeScannerExample/Pods-ReactNativeScannerExample-resources.sh\"\n";
240 | showEnvVarsInLog = 0;
241 | };
242 | /* End PBXShellScriptBuildPhase section */
243 |
244 | /* Begin PBXSourcesBuildPhase section */
245 | 13B07F871A680F5B00A75B9A /* Sources */ = {
246 | isa = PBXSourcesBuildPhase;
247 | buildActionMask = 2147483647;
248 | files = (
249 | 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */,
250 | );
251 | runOnlyForDeploymentPostprocessing = 0;
252 | };
253 | /* End PBXSourcesBuildPhase section */
254 |
255 | /* Begin XCBuildConfiguration section */
256 | 13B07F941A680F5B00A75B9A /* Debug */ = {
257 | isa = XCBuildConfiguration;
258 | baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-ReactNativeScannerExample.debug.xcconfig */;
259 | buildSettings = {
260 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
261 | CLANG_ENABLE_MODULES = YES;
262 | CURRENT_PROJECT_VERSION = 1;
263 | DEVELOPMENT_TEAM = Y425B96UBL;
264 | ENABLE_BITCODE = NO;
265 | INFOPLIST_FILE = ReactNativeScannerExample/Info.plist;
266 | IPHONEOS_DEPLOYMENT_TARGET = 15.1;
267 | LD_RUNPATH_SEARCH_PATHS = (
268 | "$(inherited)",
269 | "@executable_path/Frameworks",
270 | );
271 | MARKETING_VERSION = 1.0;
272 | OTHER_LDFLAGS = (
273 | "$(inherited)",
274 | "-ObjC",
275 | "-lc++",
276 | );
277 | PRODUCT_BUNDLE_IDENTIFIER = pushpendersingh.reactnativescanner.example;
278 | PRODUCT_NAME = ReactNativeScannerExample;
279 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
280 | SWIFT_VERSION = 5.0;
281 | VERSIONING_SYSTEM = "apple-generic";
282 | };
283 | name = Debug;
284 | };
285 | 13B07F951A680F5B00A75B9A /* Release */ = {
286 | isa = XCBuildConfiguration;
287 | baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-ReactNativeScannerExample.release.xcconfig */;
288 | buildSettings = {
289 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
290 | CLANG_ENABLE_MODULES = YES;
291 | CURRENT_PROJECT_VERSION = 1;
292 | DEVELOPMENT_TEAM = Y425B96UBL;
293 | INFOPLIST_FILE = ReactNativeScannerExample/Info.plist;
294 | IPHONEOS_DEPLOYMENT_TARGET = 15.1;
295 | LD_RUNPATH_SEARCH_PATHS = (
296 | "$(inherited)",
297 | "@executable_path/Frameworks",
298 | );
299 | MARKETING_VERSION = 1.0;
300 | OTHER_LDFLAGS = (
301 | "$(inherited)",
302 | "-ObjC",
303 | "-lc++",
304 | );
305 | PRODUCT_BUNDLE_IDENTIFIER = pushpendersingh.reactnativescanner.example;
306 | PRODUCT_NAME = ReactNativeScannerExample;
307 | SWIFT_VERSION = 5.0;
308 | VERSIONING_SYSTEM = "apple-generic";
309 | };
310 | name = Release;
311 | };
312 | 83CBBA201A601CBA00E9B192 /* Debug */ = {
313 | isa = XCBuildConfiguration;
314 | buildSettings = {
315 | ALWAYS_SEARCH_USER_PATHS = NO;
316 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
317 | CLANG_CXX_LANGUAGE_STANDARD = "c++20";
318 | CLANG_CXX_LIBRARY = "libc++";
319 | CLANG_ENABLE_MODULES = YES;
320 | CLANG_ENABLE_OBJC_ARC = YES;
321 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
322 | CLANG_WARN_BOOL_CONVERSION = YES;
323 | CLANG_WARN_COMMA = YES;
324 | CLANG_WARN_CONSTANT_CONVERSION = YES;
325 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
326 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
327 | CLANG_WARN_EMPTY_BODY = YES;
328 | CLANG_WARN_ENUM_CONVERSION = YES;
329 | CLANG_WARN_INFINITE_RECURSION = YES;
330 | CLANG_WARN_INT_CONVERSION = YES;
331 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
332 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
333 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
334 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
335 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
336 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
337 | CLANG_WARN_STRICT_PROTOTYPES = YES;
338 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
339 | CLANG_WARN_UNREACHABLE_CODE = YES;
340 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
341 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
342 | COPY_PHASE_STRIP = NO;
343 | ENABLE_STRICT_OBJC_MSGSEND = YES;
344 | ENABLE_TESTABILITY = YES;
345 | "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
346 | GCC_C_LANGUAGE_STANDARD = gnu99;
347 | GCC_DYNAMIC_NO_PIC = NO;
348 | GCC_NO_COMMON_BLOCKS = YES;
349 | GCC_OPTIMIZATION_LEVEL = 0;
350 | GCC_PREPROCESSOR_DEFINITIONS = (
351 | "DEBUG=1",
352 | "$(inherited)",
353 | );
354 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
355 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
356 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
357 | GCC_WARN_UNDECLARED_SELECTOR = YES;
358 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
359 | GCC_WARN_UNUSED_FUNCTION = YES;
360 | GCC_WARN_UNUSED_VARIABLE = YES;
361 | IPHONEOS_DEPLOYMENT_TARGET = 15.1;
362 | LD_RUNPATH_SEARCH_PATHS = (
363 | /usr/lib/swift,
364 | "$(inherited)",
365 | );
366 | LIBRARY_SEARCH_PATHS = (
367 | "\"$(SDKROOT)/usr/lib/swift\"",
368 | "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
369 | "\"$(inherited)\"",
370 | );
371 | MTL_ENABLE_DEBUG_INFO = YES;
372 | ONLY_ACTIVE_ARCH = YES;
373 | OTHER_CPLUSPLUSFLAGS = (
374 | "$(OTHER_CFLAGS)",
375 | "-DFOLLY_NO_CONFIG",
376 | "-DFOLLY_MOBILE=1",
377 | "-DFOLLY_USE_LIBCPP=1",
378 | "-DFOLLY_CFG_NO_COROUTINES=1",
379 | "-DFOLLY_HAVE_CLOCK_GETTIME=1",
380 | );
381 | REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
382 | SDKROOT = iphoneos;
383 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
384 | USE_HERMES = true;
385 | };
386 | name = Debug;
387 | };
388 | 83CBBA211A601CBA00E9B192 /* Release */ = {
389 | isa = XCBuildConfiguration;
390 | buildSettings = {
391 | ALWAYS_SEARCH_USER_PATHS = NO;
392 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
393 | CLANG_CXX_LANGUAGE_STANDARD = "c++20";
394 | CLANG_CXX_LIBRARY = "libc++";
395 | CLANG_ENABLE_MODULES = YES;
396 | CLANG_ENABLE_OBJC_ARC = YES;
397 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
398 | CLANG_WARN_BOOL_CONVERSION = YES;
399 | CLANG_WARN_COMMA = YES;
400 | CLANG_WARN_CONSTANT_CONVERSION = YES;
401 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
402 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
403 | CLANG_WARN_EMPTY_BODY = YES;
404 | CLANG_WARN_ENUM_CONVERSION = YES;
405 | CLANG_WARN_INFINITE_RECURSION = YES;
406 | CLANG_WARN_INT_CONVERSION = YES;
407 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
408 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
409 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
410 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
411 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
412 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
413 | CLANG_WARN_STRICT_PROTOTYPES = YES;
414 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
415 | CLANG_WARN_UNREACHABLE_CODE = YES;
416 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
417 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
418 | COPY_PHASE_STRIP = YES;
419 | ENABLE_NS_ASSERTIONS = NO;
420 | ENABLE_STRICT_OBJC_MSGSEND = YES;
421 | "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
422 | GCC_C_LANGUAGE_STANDARD = gnu99;
423 | GCC_NO_COMMON_BLOCKS = YES;
424 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
425 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
426 | GCC_WARN_UNDECLARED_SELECTOR = YES;
427 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
428 | GCC_WARN_UNUSED_FUNCTION = YES;
429 | GCC_WARN_UNUSED_VARIABLE = YES;
430 | IPHONEOS_DEPLOYMENT_TARGET = 15.1;
431 | LD_RUNPATH_SEARCH_PATHS = (
432 | /usr/lib/swift,
433 | "$(inherited)",
434 | );
435 | LIBRARY_SEARCH_PATHS = (
436 | "\"$(SDKROOT)/usr/lib/swift\"",
437 | "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
438 | "\"$(inherited)\"",
439 | );
440 | MTL_ENABLE_DEBUG_INFO = NO;
441 | OTHER_CPLUSPLUSFLAGS = (
442 | "$(OTHER_CFLAGS)",
443 | "-DFOLLY_NO_CONFIG",
444 | "-DFOLLY_MOBILE=1",
445 | "-DFOLLY_USE_LIBCPP=1",
446 | "-DFOLLY_CFG_NO_COROUTINES=1",
447 | "-DFOLLY_HAVE_CLOCK_GETTIME=1",
448 | );
449 | REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
450 | SDKROOT = iphoneos;
451 | USE_HERMES = true;
452 | VALIDATE_PRODUCT = YES;
453 | };
454 | name = Release;
455 | };
456 | /* End XCBuildConfiguration section */
457 |
458 | /* Begin XCConfigurationList section */
459 | 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ReactNativeScannerExample" */ = {
460 | isa = XCConfigurationList;
461 | buildConfigurations = (
462 | 13B07F941A680F5B00A75B9A /* Debug */,
463 | 13B07F951A680F5B00A75B9A /* Release */,
464 | );
465 | defaultConfigurationIsVisible = 0;
466 | defaultConfigurationName = Release;
467 | };
468 | 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "ReactNativeScannerExample" */ = {
469 | isa = XCConfigurationList;
470 | buildConfigurations = (
471 | 83CBBA201A601CBA00E9B192 /* Debug */,
472 | 83CBBA211A601CBA00E9B192 /* Release */,
473 | );
474 | defaultConfigurationIsVisible = 0;
475 | defaultConfigurationName = Release;
476 | };
477 | /* End XCConfigurationList section */
478 | };
479 | rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
480 | }
481 |
--------------------------------------------------------------------------------