Skip to content

Commit 58d834f

Browse files
feat(android): implement native camera permission callback with PermissionListener
Implement proper permission callback using PermissionListener and PermissionAwareActivity for Android. Promise now resolves based on actual user response (grant/deny) instead of immediately returning false. Changes: - Add PermissionListener callback to handle permission results - Use PermissionAwareActivity for proper permission flow - Remove react-native-permissions from example app - Update example to use built-in permission methods - Add comprehensive permission documentation to README - Add proper cleanup to prevent memory leaks Achieves feature parity with iOS. Compatible with React Native 0.81+ and Android API 23+. No breaking changes.
1 parent 2e0dc03 commit 58d834f

File tree

4 files changed

+227
-77
lines changed

4 files changed

+227
-77
lines changed

README.md

Lines changed: 166 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,44 @@ await BarcodeScanner.setFlash(false); // Turn off
240240

241241
---
242242

243+
#### `hasCameraPermission()`
244+
245+
Checks if camera permission is currently granted.
246+
247+
```typescript
248+
const hasPermission = await BarcodeScanner.hasCameraPermission();
249+
console.log('Camera permission granted:', hasPermission);
250+
```
251+
252+
**Returns:** `Promise<boolean>` - `true` if permission granted, `false` otherwise
253+
254+
---
255+
256+
#### `requestCameraPermission()`
257+
258+
Requests camera permission from the user with native promise resolution.
259+
260+
```typescript
261+
const granted = await BarcodeScanner.requestCameraPermission();
262+
if (granted) {
263+
console.log('Permission granted!');
264+
// Start scanning
265+
} else {
266+
console.log('Permission denied');
267+
// Show error or guide user to settings
268+
}
269+
```
270+
271+
**Returns:** `Promise<boolean>` - `true` if user grants permission, `false` if denied
272+
273+
**Platform Support:**
274+
-**iOS**: Fully supported with native callback
275+
-**Android**: Fully supported with native callback (API 23+)
276+
277+
**Note:** This method shows the native system permission dialog and waits for the user's response, then resolves the promise based on their choice.
278+
279+
---
280+
243281
### `CameraView`
244282

245283
React component that renders the camera preview.
@@ -303,9 +341,99 @@ useEffect(() => {
303341

304342
### Permission Handling
305343

306-
> **⚠️ Important:** We **strongly recommend** using [`react-native-permissions`](https://github.com/zoontek/react-native-permissions) for handling camera permissions in production apps. This provides better UX, more control, and unified API across platforms.
344+
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.
345+
346+
#### ✅ Using Built-in Permission Methods (Recommended)
347+
348+
The library includes native methods that handle camera permissions with proper callbacks:
349+
350+
```tsx
351+
import React, { useEffect, useState } from 'react';
352+
import { View, Text, Button, Alert } from 'react-native';
353+
import { BarcodeScanner, CameraView } from '@pushpendersingh/react-native-scanner';
354+
355+
export default function App() {
356+
const [hasPermission, setHasPermission] = useState<boolean | null>(null);
357+
const [scanning, setScanning] = useState(false);
358+
359+
useEffect(() => {
360+
checkPermission();
361+
}, []);
362+
363+
const checkPermission = async () => {
364+
const granted = await BarcodeScanner.hasCameraPermission();
365+
setHasPermission(granted);
366+
};
367+
368+
const requestPermission = async () => {
369+
const granted = await BarcodeScanner.requestCameraPermission();
370+
setHasPermission(granted);
371+
372+
if (granted) {
373+
Alert.alert('Success', 'Camera permission granted!');
374+
} else {
375+
Alert.alert(
376+
'Permission Denied',
377+
'Camera permission is required to scan barcodes'
378+
);
379+
}
380+
};
381+
382+
const startScanning = async () => {
383+
// Check permission before scanning
384+
const granted = await BarcodeScanner.hasCameraPermission();
385+
386+
if (!granted) {
387+
Alert.alert(
388+
'Permission Required',
389+
'Please grant camera permission to scan barcodes',
390+
[{ text: 'Grant Permission', onPress: requestPermission }]
391+
);
392+
return;
393+
}
394+
395+
setScanning(true);
396+
await BarcodeScanner.startScanning((barcode) => {
397+
console.log('Scanned:', barcode);
398+
BarcodeScanner.stopScanning();
399+
setScanning(false);
400+
});
401+
};
402+
403+
if (hasPermission === null) {
404+
return <Text>Checking camera permission...</Text>;
405+
}
406+
407+
if (hasPermission === false) {
408+
return (
409+
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
410+
<Text style={{ marginBottom: 20 }}>Camera permission not granted</Text>
411+
<Button title="Grant Permission" onPress={requestPermission} />
412+
</View>
413+
);
414+
}
415+
416+
return (
417+
<View style={{ flex: 1 }}>
418+
<CameraView style={{ flex: 1 }} />
419+
<Button
420+
title={scanning ? 'Stop Scanning' : 'Start Scanning'}
421+
onPress={startScanning}
422+
/>
423+
</View>
424+
);
425+
}
426+
```
307427

308-
#### Recommended: Using react-native-permissions
428+
**Key Features:**
429+
-**Cross-platform**: Works on both iOS (API 10+) and Android (API 23+)
430+
-**Promise-based**: Returns `true` when granted, `false` when denied
431+
-**Native callbacks**: Waits for actual user response from system dialog
432+
-**No dependencies**: No need for additional permission libraries
433+
434+
#### Alternative: Using react-native-permissions
435+
436+
For more advanced permission handling (checking settings, handling blocked state, etc.), you can use [`react-native-permissions`](https://github.com/zoontek/react-native-permissions):
309437

310438
```bash
311439
npm install react-native-permissions
@@ -314,47 +442,53 @@ yarn add react-native-permissions
314442
```
315443

316444
```tsx
317-
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
318-
import { Platform } from 'react-native';
445+
import { request, check, PERMISSIONS, RESULTS } from 'react-native-permissions';
446+
import { Platform, Linking } from 'react-native';
319447

320-
const requestCameraPermission = async () => {
321-
const result = await request(
322-
Platform.OS === 'ios'
323-
? PERMISSIONS.IOS.CAMERA
324-
: PERMISSIONS.ANDROID.CAMERA
325-
);
448+
const checkCameraPermission = async () => {
449+
const permission = Platform.select({
450+
ios: PERMISSIONS.IOS.CAMERA,
451+
android: PERMISSIONS.ANDROID.CAMERA,
452+
});
453+
454+
const result = await check(permission);
326455

327456
switch (result) {
328457
case RESULTS.GRANTED:
329-
return true;
458+
return 'granted';
330459
case RESULTS.DENIED:
331-
console.log('Permission denied');
332-
return false;
460+
return 'denied';
333461
case RESULTS.BLOCKED:
334-
console.log('Permission blocked - open settings');
335-
return false;
462+
return 'blocked';
336463
default:
337-
return false;
464+
return 'unavailable';
338465
}
339466
};
340-
```
341-
342-
#### Using React Native's PermissionsAndroid (Android only)
343-
344-
```tsx
345-
import { PermissionsAndroid, Platform } from 'react-native';
346467

347468
const requestCameraPermission = async () => {
348-
if (Platform.OS === 'android') {
349-
const granted = await PermissionsAndroid.request(
350-
PermissionsAndroid.PERMISSIONS.CAMERA
469+
const permission = Platform.select({
470+
ios: PERMISSIONS.IOS.CAMERA,
471+
android: PERMISSIONS.ANDROID.CAMERA,
472+
});
473+
474+
const result = await request(permission);
475+
476+
if (result === RESULTS.BLOCKED) {
477+
// User has blocked permission, guide them to settings
478+
Alert.alert(
479+
'Permission Blocked',
480+
'Please enable camera permission in settings',
481+
[
482+
{ text: 'Cancel', style: 'cancel' },
483+
{ text: 'Open Settings', onPress: () => Linking.openSettings() },
484+
]
351485
);
352-
return granted === PermissionsAndroid.RESULTS.GRANTED;
486+
return false;
353487
}
354-
return true; // iOS handles via Info.plist
488+
489+
return result === RESULTS.GRANTED;
355490
};
356491
```
357-
358492
---
359493

360494
## 📋 Supported Barcode Formats
@@ -468,17 +602,16 @@ cd example && yarn android
468602

469603
We're constantly working to improve this library. Here are some planned enhancements:
470604

605+
### Recently Completed ✅
606+
607+
- [x] **Enhanced Permission Handling** - ✅ Implemented proper native permission callback mechanism for `requestCameraPermission()` method with promise resolution based on user response (iOS & Android API 23+)
608+
471609
### Planned Features
472610

473-
- [ ] **Enhanced Permission Handling** - Implement proper native permission callback mechanism for `requestCameraPermission()` method with promise resolution based on user response
474611
- [ ] **Barcode Generation** - Add ability to generate barcodes/QR codes
475612
- [ ] **Image Analysis** - Support scanning barcodes from gallery images
476613
- [ ] **Advanced Camera Controls** - Zoom, focus, and exposure controls
477614

478-
### Known Limitations
479-
480-
- **Permission Handling**: The built-in `requestCameraPermission()` currently triggers the system dialog but doesn't wait for user response. We recommend using `react-native-permissions` for production apps. A proper implementation with permission callbacks is planned for a future release.
481-
482615
---
483616

484617
## 🤝 Contributing

android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerModule.kt

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.pushpendersingh.reactnativescanner
22

33
import android.Manifest
4+
import android.app.Activity
45
import android.content.pm.PackageManager
56
import androidx.core.app.ActivityCompat
67
import androidx.core.content.ContextCompat
@@ -9,12 +10,26 @@ import com.facebook.react.bridge.ReactApplicationContext
910
import com.facebook.react.bridge.ReactMethod
1011
import com.facebook.react.modules.core.DeviceEventManagerModule
1112
import com.facebook.react.module.annotations.ReactModule
13+
import com.facebook.react.modules.core.PermissionListener
1214

1315
@ReactModule(name = ReactNativeScannerModule.NAME)
1416
class ReactNativeScannerModule(reactContext: ReactApplicationContext) :
1517
NativeReactNativeScannerSpec(reactContext) {
1618

1719
private val cameraManager: CameraManager = CameraManager(reactContext)
20+
private var permissionPromise: Promise? = null
21+
22+
private val permissionListener = PermissionListener { requestCode, permissions, grantResults ->
23+
if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
24+
val permissionGranted = grantResults.isNotEmpty() &&
25+
grantResults[0] == PackageManager.PERMISSION_GRANTED
26+
27+
permissionPromise?.resolve(permissionGranted)
28+
permissionPromise = null
29+
return@PermissionListener true
30+
}
31+
false
32+
}
1833

1934
override fun getName(): String {
2035
return NAME
@@ -109,19 +124,24 @@ class ReactNativeScannerModule(reactContext: ReactApplicationContext) :
109124
return
110125
}
111126

112-
// Request permission
113-
ActivityCompat.requestPermissions(
114-
currentActivity,
115-
arrayOf(Manifest.permission.CAMERA),
116-
CAMERA_PERMISSION_REQUEST_CODE
117-
)
127+
// Store promise to be resolved in permission callback
128+
permissionPromise = promise
118129

119-
// The built-in `requestCameraPermission()` currently triggers the system dialog but doesn't wait for user response.
120-
// We recommend using `react-native-permissions` for production apps.
121-
// A proper implementation with permission callbacks is planned for a future release.
122-
promise.resolve(false)
130+
// Request permission using PermissionAwareActivity
131+
val permissionAwareActivity = currentActivity as? com.facebook.react.modules.core.PermissionAwareActivity
132+
if (permissionAwareActivity != null) {
133+
permissionAwareActivity.requestPermissions(
134+
arrayOf(Manifest.permission.CAMERA),
135+
CAMERA_PERMISSION_REQUEST_CODE,
136+
permissionListener
137+
)
138+
} else {
139+
promise.reject("NO_PERMISSION_AWARE_ACTIVITY", "Current activity does not implement PermissionAwareActivity")
140+
permissionPromise = null
141+
}
123142
} catch (e: Exception) {
124143
promise.reject("PERMISSION_REQUEST_ERROR", e.message, e)
144+
permissionPromise = null
125145
}
126146
}
127147

@@ -137,6 +157,7 @@ class ReactNativeScannerModule(reactContext: ReactApplicationContext) :
137157

138158
override fun invalidate() {
139159
super.invalidate()
160+
permissionPromise = null
140161
cameraManager.releaseCamera()
141162
}
142163

example/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2711,4 +2711,4 @@ SPEC CHECKSUMS:
27112711

27122712
PODFILE CHECKSUM: 39409e5f2910df3585d821342022cbe2b38bffab
27132713

2714-
COCOAPODS: 1.16.2
2714+
COCOAPODS: 1.15.2

0 commit comments

Comments
 (0)