Camera permission on launch
August 7, 2025 · AI Conversations
—
### ✅ 1. **Install the Camera Plugin**
Make sure you have the `cordova-plugin-camera` installed:
cordova plugin add cordova-plugin-camera
Also, for permissions:
cordova plugin add cordova-plugin-android-permissions
—
### ✅ 2. **Request Camera Permission on App Launch**
Add this code inside your `deviceready` event in `index.js` or similar file:
document.addEventListener('deviceready', function () {
var permissions = cordova.plugins.permissions;
permissions.checkPermission(permissions.CAMERA, function (status) {
if (!status.hasPermission) {
permissions.requestPermission(permissions.CAMERA, function (status) {
if (!status.hasPermission) {
alert("Camera permission is required to use this app.");
}
}, function () {
alert("Camera permission request denied.");
});
}
}, function () {
alert("Failed to check camera permission.");
});
});
—
### ✅ 3. **Ensure Camera Permission in `AndroidManifest.xml`**
Cordova usually adds this automatically, but to double-check, make sure your `config.xml` includes:
<platform name="android">
<edit-config file="AndroidManifest.xml" mode="merge" target="uses-permission">
<uses-permission android:name="android.permission.CAMERA" />
</edit-config>
</platform>
Or manually verify `AndroidManifest.xml` has:
<uses-permission android:name="android.permission.CAMERA" />
—
### ✅ 4. **Build and Test**
Rebuild your app to reflect changes:
cordova build android
Then install and test on a device where camera permission is **not yet granted**.
—
Let me know if you also want it to **prompt again if the user previously denied it** (which may require redirecting to app settings).
var permissions = cordova.plugins.permissions;
permissions.checkPermission(permissions.CAMERA, function (status) {
if (!status.hasPermission) {
permissions.requestPermission(permissions.CAMERA, function (status) {
if (!status.hasPermission) {
alert("Camera permission is required to use this app.");
}
}, function () {
alert("Camera permission request denied.");
});
}
}, function () {
alert("Failed to check camera permission.");
});
})
Is above is required
Let’s break it down:
—
### ✅ Why This Is Required
On **Android 6.0+ (API 23+)**, **runtime permissions** are mandatory for dangerous permissions like `CAMERA`. Cordova plugins (like the Camera plugin) **do not request permissions automatically** on app launch—you must do it manually if needed.
Your code ensures:
– ✅ Checks if camera permission is already granted.
– ✅ If not, it **asks the user for permission**.
– ❌ If denied, it shows an alert.
—
### 🔒 Without This Code
If you **only use `cordova-plugin-camera`**, it will **ask for permission only when you try to open the camera**. If your app needs to **ask up front at launch**, you must keep this code.
—
### ✅ Final Answer
Yes, **keep this code** if you want the app to:
– Prompt for **camera access at launch**
– Avoid crashing or failing later when the camera is needed
Let me know if you also want to **redirect to app settings** if permission is permanently denied.
{
var TOKEN = 'fossettscircustoken';
$(document).ready(function()
{
var eventId = -1;
var eventDateId = -1;
let ApiUrl = 'https://staging.fossettscircus.com/boxoffice/index.php/api/';
let loader=`
Loading…
`;
var scanConfig = {
preferFrontCamera : false, // iOS and Android
showFlipCameraButton : true, // iOS and Android
showTorchButton : true, // iOS and Android
torchOn: false, // Android, launch with the torch switched on (if available)
saveHistory: false, // Android, save scan history (default false)
prompt : "Place a code inside the scan area", // Android
resultDisplayDuration: 500, // Android, display scanned text for X ms. 0 suppresses it entirely, default 1500
formats : "QR_CODE", // default: all but PDF_417 and RSS_EXPANDED
//orientation : "landscape", // Android only (portrait|landscape), default unset so it rotates with the device
disableAnimations : true, // iOS
disableSuccessBeep: false // iOS and Android
};
var sendCode = function(code)
{
$('.scan').prop('disabled', true);
$('.manually').prop('disabled', true);
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('Code '+ code +' sent to boxoffice, plase wait…');
$.ajax({
url: `${ApiUrl}scan`,
method: 'POST',
data: {
token: TOKEN,
event_date_id: eventDateId,
order_code: code
},
success: function(result,status,xhr)
{
var apiResult = JSON.parse(result);
if ( apiResult.result )
{
$('#scan-result').removeClass('bg-dark').addClass('bg-success').html(apiResult.message+''+apiResult.seating+''+apiResult.tickets);
// reset input to set next code
$('[name="code"]').val(eventId +'.'+ eventDateId +'.');
}
else
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html(apiResult.message);
}
},
error: function(xhr,status,error)
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('Code '+ result.text +' could not be sent to boxoffice, please try again');
},
complete: function()
{
$('.scan').prop('disabled', false);
$('.manually').prop('disabled', false);
}
});
}
var scanError = function (error)
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html("Scanning failed: " + error);
$('#modal-scan-result').modal('show');
};
var scanSuccess = function(result)
{
if (! result.cancelled )
{
if (eventDateId > -1)
{
sendCode(result.text);
}
else
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('You must select an event date to scan tickets');
}
}
else
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('Scan was cancelled');
}
$('#modal-scan-result').modal('show');
};
$(document).on('click', '.reload-events', function()
{
$('ul.events').html(loader);
$.ajax({
url: `${ApiUrl}today`,
method: 'POST',
data: {
token: TOKEN
},
success: function(result,status,xhr)
{
console.log(result);
var events = JSON.parse(result);
if (events.result) {
$('ul.events').html('');
$.each(events.data, function(i, val)
{
var eventDatetime = new Date(val.event_datetime);
var dateOptions = { year: '2-digit', month: '2-digit', day: '2-digit' };
var timeOptions = { hour12: true, hour: '2-digit', minute: '2-digit' };
$('.events').append(''+eventDatetime.toLocaleDateString('en-GB', dateOptions)+' – '+ eventDatetime.toLocaleTimeString('en-US', timeOptions) +' Wednesday – '+ val.venue_full +'');
});
}else{
$('.events').html(`${events.message}`);
}
},
error: function(xhr,status,error)
{
console.log(error);
}
});
});
// load events click
$('.reload-events').trigger('click');
$(document).on('click', '.events .list-group-item', function()
{
$('.events .list-group-item').removeClass('active');
$(this).addClass('active');
$('#scan-info').html($(this).html());
eventId = $(this).attr('data-event-id');
eventDateId = $(this).attr('data-event-date-id');
$('[name="code"]').val(eventId +'.'+ eventDateId +'.');
// first scan on event date click
//cordova.plugins.barcodeScanner.scan(scanSuccess, scanError, scanConfig);
$('#modal-scan-result').modal('show');
});
$(document).on('click', '.scan', function()
{
cordova.plugins.barcodeScanner.scan(scanSuccess, scanError, scanConfig);
});
$(document).on('click', '.manually', function()
{
sendCode($('[name="code"]').val());
});
});
}, false);
How to adjust above we have also browser platform for cordova
—
## ✅ 1. Add Camera Permission Check (Android Only)
You should **only check/request camera permission** if the app is running on Android, because:
– `cordova.plugins.permissions` is Android-specific
– It will **fail in the browser** or throw errors on iOS if not guarded
—
## ✅ 2. Wrap Your Permission Code with Platform Check
Update your `deviceready` block like this (at the **top inside `deviceready`**):
document.addEventListener('deviceready', function () {
// Only run on Android platform
if (cordova.platformId === 'android') {
var permissions = cordova.plugins.permissions;
permissions.checkPermission(permissions.CAMERA, function (status) {
if (!status.hasPermission) {
permissions.requestPermission(permissions.CAMERA, function (status) {
if (!status.hasPermission) {
alert("Camera permission is required to scan QR codes.");
}
}, function () {
alert("Camera permission request denied.");
});
}
}, function () {
alert("Failed to check camera permission.");
});
}
// The rest of your existing jQuery logic starts here
$(document).ready(function () {
// your full code goes here...
});
}, false);
—
## ✅ 3. Prevent `cordova.plugins.barcodeScanner` from running on Browser
Wrap scanner logic like this:
$(document).on('click', '.scan', function () {
if (cordova.platformId === 'browser') {
alert('QR scanning is not supported in browser.');
} else {
cordova.plugins.barcodeScanner.scan(scanSuccess, scanError, scanConfig);
}
});
—
## ✅ Summary of What Was Added
| ✅ Change | 💡 Purpose |
|———-|————|
| `cordova.platformId === 'android'` check | Prevents camera permission logic from crashing on unsupported platforms |
| `cordova.platformId === 'browser'` in `.scan` click | Graceful fallback if user tries to scan in browser |
| Wrapped `cordova.plugins.permissions` code | Avoids breaking app in browser or iOS |
—
Let me know if you want to also add fallback for iOS permissions (they work differently) or simulate barcode scanning in browser for testing.
—
### ✅ Just Request the Permission, No Alerts
Update the `deviceready` block like this:
document.addEventListener('deviceready', function () {
if (cordova.platformId === 'android') {
var permissions = cordova.plugins.permissions;
permissions.checkPermission(permissions.CAMERA, function (status) {
if (!status.hasPermission) {
permissions.requestPermission(
permissions.CAMERA,
function (status) {
// Permission granted or not — don't alert, Android will handle it
},
function () {
// No need to alert
}
);
}
});
}
$(document).ready(function () {
// your full code here...
});
}, false);
—
### ✅ Why This Works
– If the user **hasn't granted** the permission, Android will show the **default system permission dialog** (with **Allow** / **Deny**).
– If already granted, nothing happens — your app continues silently.
– No `alert()` or custom dialog will interrupt the flow.
—
Let me know if you want to **prompt again** if the user permanently denied permission (which requires going to **app settings**, because Android won’t show the popup again after "Don't ask again").
– ✅ **Show only splash screen on launch**
– ✅ **Ask for camera permission after splash**
– ❌ **If user denies camera permission**, automatically **close the app**
—
### ✅ Step-by-Step Implementation
#### 1. **Keep the splash screen until permission is resolved**
Make sure you're using the splash screen plugin:
cordova plugin add cordova-plugin-splashscreen
Then add to `config.xml`:
<preference name="SplashScreenDelay" value="10000" /> <!-- delay in ms -->
<preference name="FadeSplashScreenDuration" value="500" />
<preference name="AutoHideSplashScreen" value="false" />
> This disables auto-hide. We'll hide it manually after checking permission.
—
#### 2. **Request camera permission after deviceready**
Update your `index.js` or equivalent script:
document.addEventListener('deviceready', function () {
if (cordova.platformId === 'android') {
var permissions = cordova.plugins.permissions;
permissions.checkPermission(permissions.CAMERA, function (status) {
if (!status.hasPermission) {
// Request permission
permissions.requestPermission(permissions.CAMERA, function (status) {
if (status.hasPermission) {
navigator.splashscreen.hide(); // Hide splash if granted
} else {
navigator.app.exitApp(); // Close app if denied
}
}, function () {
navigator.app.exitApp(); // Close app if error or denied
});
} else {
navigator.splashscreen.hide(); // Already granted
}
});
} else {
navigator.splashscreen.hide(); // iOS or browser fallback
}
}, false);
—
### ✅ Plugin Requirements
Ensure these plugins are installed:
cordova plugin add cordova-plugin-android-permissions
cordova plugin add cordova-plugin-splashscreen
cordova plugin add cordova-plugin-globalization
cordova plugin add cordova-plugin-dialogs
(`cordova-plugin-dialogs` is optional unless you want native confirms)
—
### ✅ Summary
| ✅ Action | 💡 Result |
|———-|———–|
| `AutoHideSplashScreen = false` | Keeps splash until permission handled |
| Check/request `CAMERA` permission | Android default dialog appears |
| If granted | Hide splash and continue app |
| If denied | Exit app using `navigator.app.exitApp()` |
Let me know if you want to show a confirmation before exit or handle other permissions like storage too.
{
// Only run on Android platform
if (cordova.platformId === 'android') {
var permissions = cordova.plugins.permissions;
permissions.checkPermission(permissions.CAMERA, function (status) {
if (!status.hasPermission) {
permissions.requestPermission(
permissions.CAMERA,
function (status) {
// Permission granted or not — don't alert, Android will handle it
},
function () {
// No need to alert
}
);
}
});
}
let TOKEN = 'fossettscircustoken';
$(document).ready(function()
{
var eventId = -1;
var eventDateId = -1;
let ApiUrl = 'https://staging.fossettscircus.com/boxoffice/index.php/api/';
let loader=`
Loading…
`;
var scanConfig = {
preferFrontCamera : false, // iOS and Android
showFlipCameraButton : true, // iOS and Android
showTorchButton : true, // iOS and Android
torchOn: false, // Android, launch with the torch switched on (if available)
saveHistory: false, // Android, save scan history (default false)
prompt : "Place a code inside the scan area", // Android
resultDisplayDuration: 500, // Android, display scanned text for X ms. 0 suppresses it entirely, default 1500
formats : "QR_CODE", // default: all but PDF_417 and RSS_EXPANDED
//orientation : "landscape", // Android only (portrait|landscape), default unset so it rotates with the device
disableAnimations : true, // iOS
disableSuccessBeep: false // iOS and Android
};
var sendCode = function(code)
{
$('.scan').prop('disabled', true);
$('.manually').prop('disabled', true);
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('Code '+ code +' sent to boxoffice, plase wait…');
$.ajax({
url: `${ApiUrl}scan`,
method: 'POST',
data: {
token: TOKEN,
event_date_id: eventDateId,
order_code: code
},
success: function(result,status,xhr)
{
var apiResult = JSON.parse(result);
if ( apiResult.result )
{
$('#scan-result').removeClass('bg-dark').addClass('bg-success').html(apiResult.message+''+apiResult.seating+''+apiResult.tickets);
// reset input to set next code
$('[name="code"]').val(eventId +'.'+ eventDateId +'.');
}
else
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html(apiResult.message);
}
},
error: function(xhr,status,error)
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('Code '+ result.text +' could not be sent to boxoffice, please try again');
},
complete: function()
{
$('.scan').prop('disabled', false);
$('.manually').prop('disabled', false);
}
});
}
var scanError = function (error)
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html("Scanning failed: " + error);
$('#modal-scan-result').modal('show');
};
var scanSuccess = function(result)
{
if (! result.cancelled )
{
if (eventDateId > -1)
{
sendCode(result.text);
}
else
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('You must select an event date to scan tickets');
}
}
else
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('Scan was cancelled');
}
$('#modal-scan-result').modal('show');
};
$(document).on('click', '.reload-events', function()
{
$('ul.events').html(loader);
$.ajax({
url: `${ApiUrl}today`,
method: 'POST',
data: {
token: TOKEN
},
success: function(result,status,xhr)
{
console.log(result);
var events = JSON.parse(result);
if (events.result) {
$('ul.events').html('');
$.each(events.data, function(i, val)
{
var eventDatetime = new Date(val.event_datetime);
var dateOptions = { year: '2-digit', month: '2-digit', day: '2-digit' };
var timeOptions = { hour12: true, hour: '2-digit', minute: '2-digit' };
$('.events').append(''+eventDatetime.toLocaleDateString('en-GB', dateOptions)+' – '+ eventDatetime.toLocaleTimeString('en-US', timeOptions) +' Wednesday – '+ val.venue_full +'');
});
}else{
$('.events').html(`${events.message}`);
}
},
error: function(xhr,status,error)
{
console.log(error);
}
});
});
// load events click
$('.reload-events').trigger('click');
$(document).on('click', '.events .list-group-item', function()
{
$('.events .list-group-item').removeClass('active');
$(this).addClass('active');
$('#scan-info').html($(this).html());
eventId = $(this).attr('data-event-id');
eventDateId = $(this).attr('data-event-date-id');
$('[name="code"]').val(eventId +'.'+ eventDateId +'.');
// first scan on event date click
//cordova.plugins.barcodeScanner.scan(scanSuccess, scanError, scanConfig);
$('#modal-scan-result').modal('show');
});
$(document).on('click', '.scan', function()
{
cordova.plugins.barcodeScanner.scan(scanSuccess, scanError, scanConfig);
});
$(document).on('click', '.manually', function()
{
sendCode($('[name="code"]').val());
});
});
}, false);
How to adjust this code
1. ✅ The splash screen shows on app launch
2. ✅ Camera permission is requested **before anything else happens**
3. ❌ If the permission is denied, the app **closes immediately**
4. ✅ The rest of your logic (like `.reload-events`, scanning, etc.) only runs **after** camera permission is granted
—
### ✅ **Full Adjusted Code (Cleaned + Functional)**
Here’s your updated code with comments and permission logic placed correctly:
document.addEventListener('deviceready', function () {
if (cordova.platformId === 'android') {
var permissions = cordova.plugins.permissions;
permissions.checkPermission(permissions.CAMERA, function (status) {
if (!status.hasPermission) {
// Request permission
permissions.requestPermission(
permissions.CAMERA,
function (status) {
if (status.hasPermission) {
navigator.splashscreen.hide(); // Hide splash after permission
initializeApp(); // Proceed with your app
} else {
navigator.app.exitApp(); // Exit if denied
}
},
function () {
navigator.app.exitApp(); // Exit on request error
}
);
} else {
navigator.splashscreen.hide();
initializeApp(); // Permission already granted
}
});
} else {
navigator.splashscreen.hide(); // For browser/iOS fallback
initializeApp();
}
}, false);
—
### 🔁 Wrap your jQuery app logic inside a new function `initializeApp()`
function initializeApp() {
let TOKEN = 'fossettscircustoken';
let eventId = -1;
let eventDateId = -1;
let ApiUrl = 'https://staging.fossettscircus.com/boxoffice/index.php/api/';
let loader = `<li class="text-center"><div class="spinner-border text-primary" role="status">
<span class="sr-only">Loading...</span>
</div></li>`;
let scanConfig = {
preferFrontCamera: false,
showFlipCameraButton: true,
showTorchButton: true,
torchOn: false,
saveHistory: false,
prompt: "Place a code inside the scan area",
resultDisplayDuration: 500,
formats: "QR_CODE",
disableAnimations: true,
disableSuccessBeep: false
};
$(document).ready(function () {
var sendCode = function (code) {
$('.scan').prop('disabled', true);
$('.manually').prop('disabled', true);
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('Code <u class="bg-primary text-white p-1">' + code + '</u> sent to boxoffice, please wait...');
$.ajax({
url: `${ApiUrl}scan`,
method: 'POST',
data: {
token: TOKEN,
event_date_id: eventDateId,
order_code: code
},
success: function (result, status, xhr) {
var apiResult = JSON.parse(result);
if (apiResult.result) {
$('#scan-result').removeClass('bg-dark').addClass('bg-success').html(apiResult.message + '<br>' + apiResult.seating + '<br>' + apiResult.tickets);
$('[name="code"]').val(eventId + '.' + eventDateId + '.');
} else {
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html(apiResult.message);
}
},
error: function (xhr, status, error) {
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('Code could not be sent to boxoffice, please try again');
},
complete: function () {
$('.scan').prop('disabled', false);
$('.manually').prop('disabled', false);
}
});
};
var scanError = function (error) {
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html("Scanning failed: " + error);
$('#modal-scan-result').modal('show');
};
var scanSuccess = function (result) {
if (!result.cancelled) {
if (eventDateId > -1) {
sendCode(result.text);
} else {
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('You must select an event date to scan tickets');
}
} else {
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('Scan was cancelled');
}
$('#modal-scan-result').modal('show');
};
$(document).on('click', '.reload-events', function () {
$('ul.events').html(loader);
$.ajax({
url: `${ApiUrl}today`,
method: 'POST',
data: { token: TOKEN },
success: function (result, status, xhr) {
var events = JSON.parse(result);
if (events.result) {
$('ul.events').html('');
$.each(events.data, function (i, val) {
var eventDatetime = new Date(val.event_datetime);
var dateOptions = { year: '2-digit', month: '2-digit', day: '2-digit' };
var timeOptions = { hour12: true, hour: '2-digit', minute: '2-digit' };
$('.events').append('<li class="list-group-item text-center" data-event-id="' + val.event_id + '" data-event-date-id="' + val.event_date_id + '">' + eventDatetime.toLocaleDateString('en-GB', dateOptions) + ' - ' + eventDatetime.toLocaleTimeString('en-US', timeOptions) + ' Wednesday - ' + val.venue_full + '</li>');
});
} else {
$('.events').html(`<li class="no-result-item text-center bg-info text-white">${events.message}</li>`);
}
},
error: function (xhr, status, error) {
console.log(error);
}
});
});
$('.reload-events').trigger('click');
$(document).on('click', '.events .list-group-item', function () {
$('.events .list-group-item').removeClass('active');
$(this).addClass('active');
$('#scan-info').html($(this).html());
eventId = $(this).attr('data-event-id');
eventDateId = $(this).attr('data-event-date-id');
$('[name="code"]').val(eventId + '.' + eventDateId + '.');
$('#modal-scan-result').modal('show');
});
$(document).on('click', '.scan', function () {
cordova.plugins.barcodeScanner.scan(scanSuccess, scanError, scanConfig);
});
$(document).on('click', '.manually', function () {
sendCode($('[name="code"]').val());
});
}); // end of jQuery ready
}
—
### ✅ Plugins You Need
Ensure these plugins are added:
cordova plugin add cordova-plugin-splashscreen
cordova plugin add cordova-plugin-android-permissions
cordova plugin add phonegap-plugin-barcodescanner
—
### ✅ Result:
– 📱 Splash screen stays visible.
– 🛑 App won't proceed unless camera permission is granted.
– ❌ If denied, app exits.
– ✅ Your full logic executes only after permission is granted.
Let me know if you want to auto-redirect to **app settings** if the user selects "Don't ask again."