Hi all I have an app with a feature that allows the user to take a picture during a task.
Recently when asking for camera permissions for the first time the device is not showing the native alert but rather deferring to my secondary alert that is supposed to be used if the user had denied or changed their permissions settings after the first attempt.
My understanding is that when a device is asked for the first time iOS will supply the permissions alert similar to this
I have this in my info.plist
<key>NSCameraUsageDescription</key>
<string>Allow access to your camera to take pictures of your dog!</string>
code when the user taps the camera button in app (solution at bottom)
function takePhoto() {
check(
Platform.select({
ios: PERMISSIONS.IOS.CAMERA,
android: PERMISSIONS.ANDROID.CAMERA,
})
)
.then(async (response) => {
if (response == 'unavailable') {
alert('Camera is not available on this device');
return;
}
const userId = auth().currentUser.uid;
let image = null;
const mapAPI = new MapAPI(firestore(), userId, storage);
const showSettingsAlert = (title, message) => {
Alert.alert(
title,
message,
[
{
text: translations['settings.goto'],
onPress: async () => {
Linking.openSettings();
},
},
{
text: translations['cancel'],
onPress: () => {
console.log('Cancel Pressed');
},
style: 'cancel',
},
],
{ cancelable: true }
);
};
if (response != RESULTS.GRANTED) {
showSettingsAlert(
translations['permissions.cameratitle'],
translations['permissions.cameradesc']
);
return;
}
I've been trying to tackle this for a while and appreciate any help. Thanks!
You are testing for response != RESULTS.GRANTED. So, if it is “not determined” (the status prior to asking the user’s permissions), that would result in your alert. We generally show our custom alert if the status is “denied” or “restricted”, rather than not “granted”.
I realized with the help from another overflower that I had not requested to access the camera before calling the custom alert.
Here is the solution that worked in my instance.
const takePhoto = async () => {
const imageProps = {
mediaType: 'photo',
width: 400,
height: 400,
writeTempFile: true,
};
const userId = auth().currentUser.uid;
let image = null;
const mapAPI = new MapAPI(firestore(), userId, storage);
try {
image = await ImageCropPicker.openCamera(imageProps);
} catch (error) {
console.log(error);
const response = await check(
Platform.select({
ios: PERMISSIONS.IOS.CAMERA,
android: PERMISSIONS.ANDROID.CAMERA,
})
);
if (response !== RESULTS.GRANTED && response !== RESULTS.UNAVAILABLE) {
showSettingsAlert(
translations['permissions.cameratitle'],
translations['permissions.cameradesc']
);
}
}
if (!image) {
return;
}
Related
I am having an issue where the local notification is not displaying in android though it is displaying in IOS. I have created permissions and battery optimization check to see if that is the problem. The notification does make a sound and appear shows a little square in the upper left that if I click and drag does show the local notification. I am using an emulator right now and I have tried it on a real device.
This is the way I am calling the notification.
<Button
title="Trigger Push Notification"
size="large"
onPress={() => onDisplayNotification('default', 'Default Channel', 'Spotback
Android', 'Local push notification')}
/>
This is is the Notifee component.
import notifee, { AndroidStyle, AuthorizationStatus, Notification } from '#notifee/react-native';
import { Alert } from 'react-native';
export const onDisplayNotification = async (id, name, title, body, smallIcon?) => {
// Request permissions (required for iOS)
await notifee.requestPermission();
const settings = await notifee.getNotificationSettings();
const batteryOptimizationEnabled = await notifee.isBatteryOptimizationEnabled();
if (batteryOptimizationEnabled) {
// 2. ask your users to disable the feature
Alert.alert(
'Restrictions Detected',
'To ensure notifications are delivered, please disable battery optimization for the app.',
[
// 3. launch intent to navigate the user to the appropriate screen
{
text: 'OK, open settings',
onPress: async () => await notifee.openBatteryOptimizationSettings(),
},
{
text: 'Cancel',
onPress: () => console.log('Cancel Pressed'),
style: 'cancel',
},
],
{ cancelable: false }
);
}
if (settings.authorizationStatus == AuthorizationStatus.AUTHORIZED) {
console.log('Notification permissions has been authorized');
} else if (settings.authorizationStatus == AuthorizationStatus.DENIED) {
console.log('Notification permissions has been denied');
}
// }
// Create a channel (required for Android)
const channelId = await notifee.createChannel({
id,
name,
});
await notifee.displayNotification({
title,
body,
android: {
channelId,
smallIcon,
pressAction: {
id,
},
},
});
};
Please change your channel id (default) and channel name (Default Channel) to something else like:
id: custom_channel
name: custom_channel
I'm trying to enable the bluetooth when its off using react native by this code
when its turned on the modal asking permission using bluetooth is showed up, but i want to show modal alert again when the bluetooth turned off. So how to show the modal it can be redirect to settings on both android and ios ?
this.setState({ isEnabledSwitch: !this.state.isEnabledSwitch })
const bluetoothPermission = Platform.OS === 'ios' ? PERMISSIONS.IOS.BLUETOOTH_PERIPHERAL : PERMISSIONS.ANDROID.BLUETOOTH_PERIPHERAL;
request(bluetoothPermission).then((result) => {
console.log('result', result);
const title = '“Wynn Resorts” would like to use Bluetooth';
const description = 'Wynn Resorts needs your Bluetooth enabled to use Digital Key features to access your room with your phone. Please go to Settings > Wynn Resorts and turn on Bluetooth in order to allow access.';
Alert.alert(
title,
description,
[
{
text: 'Cancel',
onPress: () => {
this.isDialogShown = false;
// this.gotoWayfinding();
}
},
{
text: 'Settings',
onPress: () => {
this.isDialogShown = false;
if (Platform.OS === 'ios') {
Linking.openURL('app-settings:')
} else {
this.props.navigation.goBack();
NativeModules.OpenSettings.openNetworkSettings(data => {/*This is intentional*/ });
}
}
}
],
{ cancelable: true }
);
}).catch((error) => {/*This is intentional*/ })
}
but when i try in real device the xcode show logs of
2022-03-14 19:19:05.493649+0700 appsTrial[657:117152] [CoreBluetooth] XPC connection invalid
how to solve this problem?
I am asking for location permission for my Android React Native app. I am using the npm package: react-native-permissions. I have created a custom hook to do this.
My implementation for iOS works perfect. While trying a similar approach on Android, the dialog that asks the user for location permission never pops up. On my initial check for the permission, my app reports that permission is already granted!! But how?
I include this in my AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
and this is my hook that asks for permission:
export default (setPermissionsGranted = () => {}, setPermissionError = () => {}) => {
const appState = useRef(AppState.currentState)
const [shouldCheckPermission, setShoudCheckPermission] = useState(false)
useEffect(() => {
let isMounted = true
const handleAppState = (nextAppState) => {
if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
// the app has come into the foreground. Change a boolean state variable to trigger
// handlePermission() again.
if (isMounted) setShoudCheckPermission(!shouldCheckPermission)
}
appState.current = nextAppState
}
const handlePermissionStatus = (result) => {
const status = {
unavailable: () => {},
denied: async () => {
const res = await request(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION)
if (isMounted) {
if (res === RESULTS.GRANTED) {
setPermissionsGranted(true)
} else {
setPermissionsGranted(false)
}
}
},
limited: () => {
// todo find out what limited entails
if (isMounted) setPermissionsGranted(true)
},
granted: async () => {
if (isMounted) setPermissionsGranted(true)
},
blocked: () => {
if (isMounted) setPermissionsGranted(false)
},
}
return status[result]
}
AppState.addEventListener('change', handleAppState)
const handlePermission = async () => {
console.log('permissions code is run')
try {
const res = await check(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION)
console.log('res', res)
handlePermissionStatus(res)()
} catch (err) {
throw err
}
}
handlePermission().catch((e) => {
if (isMounted) setPermissionError(e.message)
})
return () => {
AppState.removeEventListener('change', handleAppState)
isMounted = false
}
}, [shouldCheckPermission])
}
Any explanation as to why the user never get's asked and the permission is automatically granted?
Another peculiarity, I have commented out all of the code that requests permission for Location on Android, restarted the metro server, uninstalled the app and then re-installed it. The request-permissions tag is still in the AndroidManifest.xml. Apparently that's all I needed to do and now permissions are AUTOMATICALLY granted!
My understanding is that this is a dangerous permission and should not be granted automatically but Android is treating it as a safe-permission. 🤷🏻♂️
Thanks in advance.
As the title says, I'm trying to upload Image to firebase in react native. I'm using react-native-image-picker and firebase modules for that. My code goes as: (Only including the "main" parts for clarity)
import ImagePicker from 'react-native-image-picker';
...
//called on pressing a button
onChooseImagePress = async () => {
let result = await ImagePicker.open({ //error occurs here
takePhoto: true,
useLastPhoto: true,
chooseFromLibrary: true
});
if (!result.cancelled) {
this.uploadImage(result.uri, "test-image")
.then(() => {
Alert.alert("Success");
})
.catch((error) => {
Alert.alert(error);
});
}
}
uploadImage = async (uri, imageName) => {
const response = await fetch(uri);
const blob = await response.blob();
var ref = firebase.storage().ref('images').child("userName/" + imageName);
return ref.put(blob);
}
....
Issue:
I am getting this error: undefined is not a function. Here's a screenshot of the same:
I'm not sure what it even means, since ImagePicker has an open function. Please note that I have provided the desired permissions. So it is not an issue due to that. Please help me resolve this. Thanks...
Are you using React-native ImagePicker? There is no open in the API document.
API Reference of react-native-image-picker
This is the default example of getting the value of the selected image you want.
import ImagePicker from 'react-native-image-picker';
// More info on all the options is below in the API Reference... just some common use cases shown here
const options = {
title: 'Select Avatar',
customButtons: [{ name: 'fb', title: 'Choose Photo from Facebook' }],
storageOptions: {
skipBackup: true,
path: 'images',
},
};
/**
* The first arg is the options object for customization (it can also be null or omitted for default options),
* The second arg is the callback which sends object: response (more info in the API Reference)
*/
ImagePicker.launchImageLibrary(options, (response) => {
console.log('Response = ', response);
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.error) {
console.log('ImagePicker Error: ', response.error);
} else if (response.customButton) {
console.log('User tapped custom button: ', response.customButton);
} else {
const source = { uri: response.uri };
// You can also display the image using data:
// const source = { uri: 'data:image/jpeg;base64,' + response.data };
this.setState({
avatarSource: source,
});
}
});
I ran into a bug whenever I run my React Native app on an Android device (physical and emulator). Yet, no problem at all on iOS. These functions are supposed to scan the database table for user handles and return an object if the handle already exists.
This is what the error looks like:
TypeError: Cannot read property 'handle' of null
at exports.handler (/var/task/index.js:7:36)
I'm using React Native, AWS Lambda, and EXPO.
This code lives within dbfunctions.js on the front end.
export async function scanHandles(){
return new Promise((resolve, reject) => {
let { auth } = store.getState()
let reqBody = {
userId: auth.user.username,
handle: auth.handle_update,
}
let path = '/u/scan-handle'
let myInit = {
headers: { 'Content-Type': 'application/json' },
body: reqBody,
}
console.log('myInit', myInit)
console.log('handle', auth.handle_update)
API.get(apiName, path, myInit)
.then((resp) => {
// if false, then handle does not exist
// if true, then handle already exists
resolve(resp)
})
.catch((error) => {
console.warn('Scan Handle', error)
reject(error)
})
})
}
Console logging auth.handle_update does print out the expected string. myInit also prints out the expected object.
On the back end, I'm using this for my scan:
const AWS = require("aws-sdk");
const docClient = new AWS.DynamoDB.DocumentClient({ region: "us-west-1" });
exports.handler = (event, context, callback) => {
let e = JSON.parse(event.body);
var params = {
TableName: event.stageVariables.user,
FilterExpression: "handle = :handle",
ExpressionAttributeValues: { ":handle": e.handle }
};
docClient.scan(params, function(err, data) {
if (err) {
console.log("ERROR:", err);
let response = {
statusCode: err.statusCode,
headers: {},
body: JSON.stringify(err)
};
callback(response);
}
if (data.Count >= 1) {
// if user name exists
// call back handle exists response
let handleExistsResponse = {
statusCode: 200,
body: JSON.stringify({ Success: true })
};
callback(null, handleExistsResponse);
} else {
let response = {
statusCode: 200,
body: JSON.stringify({ Success: false })
};
callback(null, response);
}
});
};
Any idea as to why this would work on iOS and not Android?
EDIT:
Upon further testing, let e = JSON.parse(event.body) is returning null. So I console logged event and got a big ol object. Within this object, I found body and it's still null. So the body object isn't being passed it properly. Still confused about it working on iOS and not Android.
Did it!
Okay so API.get doesn't like body's being passed in. Instead, it wants a query parameter. So the lambda params looks like:
var params = {
TableName: event.stageVariables.user,
FilterExpression: "handle = :handle",
ExpressionAttributeValues: {
":handle": event["queryStringParameters"]["handle"]
}
};
And the front end function is:
export async function scanHandles(){
return new Promise((resolve, reject) => {
let { auth } = store.getState()
let handle = auth.handle_update
let path = `/u/scan-handle?handle=${handle}`
let myInit = {
headers: { 'Content-Type': 'application/json' },
}
API.get(apiName, path, myInit)
.then((resp) => {
// if false, then handle does not exist
// if true, then handle already exists
resolve(resp)
})
.catch((error) => {
console.warn('Scan Handle', error)
reject(error)
})
})
}
Works on both iOS and Android. Wonder why it wasn't working before?