ScaleRocket/Mobile

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

PlatformBehavior
iOSShows a system dialog once. If denied, user must go to Settings
AndroidShows 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:

TimingAcceptance RateWhen to Use
On app launchLowNever — users don't know why yet
Before first useHighWhen user triggers a feature
After explanationHighestShow 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.

On this page