Permissions
Requesting app permissions for camera, notifications, and location with graceful degradation patterns.
Permissions
Mobile apps must request permission before accessing device capabilities like the camera, location, or notifications. ScaleRocket Mobile uses Expo's permissions API for a consistent cross-platform experience.
How Permissions Work
| Platform | Behavior |
|---|---|
| iOS | Shows a system dialog once. If denied, user must go to Settings |
| Android | Shows a system dialog. Can be "denied permanently" after two denials |
Both platforms follow the same pattern: check, request, handle denial.
Check Before Requesting
Always check the current status before showing a prompt:
import * as ImagePicker from "expo-image-picker";
async function pickImage() {
const { status } = await ImagePicker.getMediaLibraryPermissionsAsync();
if (status !== "granted") {
const { status: newStatus } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (newStatus !== "granted") {
Alert.alert(
"Permission Required",
"Please enable photo access in Settings to upload images.",
[
{ text: "Cancel", style: "cancel" },
{ text: "Open Settings", onPress: () => Linking.openSettings() },
]
);
return;
}
}
// Permission granted — proceed
const result = await ImagePicker.launchImageLibraryAsync();
}Common Permissions
Camera
import { Camera } from "expo-camera";
const [permission, requestPermission] = Camera.useCameraPermissions();
if (!permission?.granted) {
return (
<View>
<Text>Camera access is required to take photos</Text>
<Button title="Grant Permission" onPress={requestPermission} />
</View>
);
}Push Notifications
import * as Notifications from "expo-notifications";
async function requestNotificationPermission() {
const { status: existing } = await Notifications.getPermissionsAsync();
let finalStatus = existing;
if (existing !== "granted") {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== "granted") {
console.log("Notification permission denied");
return false;
}
return true;
}Location
import * as Location from "expo-location";
async function requestLocation() {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== "granted") {
Alert.alert("Permission Denied", "Location access is needed for this feature.");
return null;
}
const location = await Location.getCurrentPositionAsync({});
return location;
}When to Request
Timing matters for permission acceptance rates:
| Timing | Acceptance Rate | When to Use |
|---|---|---|
| On app launch | Low | Never — users don't know why yet |
| Before first use | High | When user triggers a feature |
| After explanation | Highest | Show why you need it, then ask |
Pre-Permission Screen Pattern
Show an explanation before the system dialog:
function CameraPermissionScreen({ onGranted }: { onGranted: () => void }) {
const [permission, requestPermission] = Camera.useCameraPermissions();
if (permission?.granted) {
onGranted();
return null;
}
return (
<View style={{ flex: 1, justifyContent: "center", padding: 24 }}>
<Ionicons name="camera" size={64} color="#3B82F6" />
<Text style={{ fontSize: 20, fontWeight: "600", marginTop: 16 }}>
Camera Access
</Text>
<Text style={{ color: "#6B7280", marginTop: 8 }}>
We need camera access to scan QR codes and take profile photos.
</Text>
<Button title="Enable Camera" onPress={requestPermission} />
</View>
);
}Graceful Degradation
Always provide an alternative when permission is denied:
function AvatarUpload() {
const [permission] = ImagePicker.useMediaLibraryPermissions();
if (!permission?.granted) {
return (
<View>
<Avatar name={user.name} size="lg" />
<Text style={{ color: "#9CA3AF", marginTop: 8 }}>
Enable photo access in Settings to upload a profile picture
</Text>
</View>
);
}
return <AvatarWithUpload user={user} />;
}iOS Info.plist Descriptions
iOS requires human-readable descriptions for each permission. Add them in app.json:
{
"expo": {
"ios": {
"infoPlist": {
"NSCameraUsageDescription": "We use the camera to take profile photos and scan QR codes",
"NSPhotoLibraryUsageDescription": "We access your photos to let you upload a profile picture",
"NSLocationWhenInUseUsageDescription": "We use your location to show nearby services",
"NSFaceIDUsageDescription": "We use Face ID to secure your account"
}
}
}
}Important: Apps with vague or missing permission descriptions will be rejected by Apple.