Skip to main content

Permissions

Background location tracking requires runtime permissions on both Android and iOS. This guide covers the permission model for each platform, the recommended hook-based approach, and the manual PermissionsAndroid alternative for Android.

The useLocationPermissions hook provides a unified cross-platform permission API. It handles the correct multi-step sequencing on both platforms automatically.

import {
useLocationPermissions,
LocationPermissionStatus,
NotificationPermissionStatus,
} from '@gabriel-sisjr/react-native-background-location';

function PermissionGate({ children }: { children: React.ReactNode }) {
const {
permissionStatus,
requestPermissions,
checkPermissions,
isRequesting,
} = useLocationPermissions();

if (!permissionStatus.location.hasPermission) {
return (
<View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
<Text>Location permission is required to track trips.</Text>
<Button
title={isRequesting ? 'Requesting...' : 'Grant Permissions'}
onPress={requestPermissions}
disabled={isRequesting}
/>
{permissionStatus.location.status === LocationPermissionStatus.BLOCKED && (
<Button
title="Open Settings"
onPress={() => Linking.openSettings()}
/>
)}
</View>
);
}

return <>{children}</>;
}

Permission State Shape

The permissionStatus object returned by the hook uses a granular nested structure:

interface PermissionState {
hasAllPermissions: boolean; // true only when both location AND notification are granted
location: {
hasPermission: boolean; // true if location access is granted
status: LocationPermissionStatus;
canRequestAgain: boolean;
};
notification: {
hasPermission: boolean; // true if notification permission is granted
status: NotificationPermissionStatus;
canRequestAgain: boolean; // true only when status is UNDETERMINED
};
}

Note: hasAllPermissions requires both location and notification permissions. If you only need to check location (which is sufficient for tracking), use permissionStatus.location.hasPermission instead.

Return Values

PropertyTypeDescription
permissionStatusPermissionStateCurrent state of location and notification permissions
requestPermissions() => Promise<boolean>Requests all permissions. Returns true if location is granted
checkPermissions() => Promise<boolean>Checks current status without prompting. Returns true if location is granted
isRequestingbooleantrue while a permission request is in progress

What requestPermissions Does

On Android, requestPermissions() executes three sequential steps:

  1. Requests ACCESS_FINE_LOCATION + ACCESS_COARSE_LOCATION (foreground)
  2. Requests ACCESS_BACKGROUND_LOCATION separately (Android 10+)
  3. Requests POST_NOTIFICATIONS (Android 13+) or checks via native module (older versions)

On iOS, requestPermissions() executes two sequential steps:

  1. Requests location permission with WhenInUse-to-Always escalation via CLLocationManager
  2. Requests notification permission via UNUserNotificationCenter

Notification permission denial is non-blocking -- requestPermissions() returns true as long as location permission is granted. Check permissionStatus.notification separately if you need notification state.


Android Permission Model

Android uses a layered permission system where each permission level must be requested separately and in the correct order.

Required Manifest Permissions

These must be declared in AndroidManifest.xml (see Installation):

PermissionWhen RequiredPurpose
ACCESS_FINE_LOCATIONAlwaysGPS-based location
ACCESS_COARSE_LOCATIONAlwaysNetwork-based location
ACCESS_BACKGROUND_LOCATIONAndroid 10+ (API 29+)Location when app is not visible
FOREGROUND_SERVICEAlwaysRequired for background service
FOREGROUND_SERVICE_LOCATIONAndroid 14+ (API 34+)Foreground service type declaration
POST_NOTIFICATIONSAndroid 13+ (API 33+)Show foreground service notification

Runtime Permission Flow

The correct order for requesting permissions on Android is critical. Requesting background and foreground permissions together will silently fail on Android 11+.

Step 1: Foreground Location
ACCESS_FINE_LOCATION + ACCESS_COARSE_LOCATION
|
v
Step 2: Background Location (Android 10+)
ACCESS_BACKGROUND_LOCATION
(must be a separate request)
|
v
Step 3: Notification (Android 13+)
POST_NOTIFICATIONS

Android API Level Differences

API LevelAndroid VersionPermission Behavior
24-287.0 -- 9.0Foreground permission grants background access automatically
2910Background location requires separate ACCESS_BACKGROUND_LOCATION request
30+11+Background location must be requested separately (combined request silently fails)
33+13+Notification permission (POST_NOTIFICATIONS) required at runtime
34+14+FOREGROUND_SERVICE_LOCATION type must be declared

Manual Android Permissions (Without Hook)

If you prefer to manage permissions manually using React Native's PermissionsAndroid API:

import { PermissionsAndroid, Platform, Alert, Linking } from 'react-native';

async function requestLocationPermissions(): Promise<boolean> {
if (Platform.OS !== 'android') return true;

// Step 1: Request foreground permissions
const foreground = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
]);

const foregroundGranted =
foreground['android.permission.ACCESS_FINE_LOCATION'] === 'granted' ||
foreground['android.permission.ACCESS_COARSE_LOCATION'] === 'granted';

if (!foregroundGranted) {
return false;
}

// Step 2: Request background permission (Android 10+)
if (Number(Platform.Version) >= 29) {
// Show rationale before the system dialog
await new Promise<void>((resolve) => {
Alert.alert(
'Background Location',
'To track your location when the app is closed, please select '
+ '"Allow all the time" on the next screen.',
[{ text: 'Continue', onPress: () => resolve() }]
);
});

const background = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION
);

if (background !== 'granted') {
return false;
}
}

// Step 3: Request notification permission (Android 13+)
if (Number(Platform.Version) >= 33) {
await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
);
// Notification denial is non-blocking for tracking
}

return true;
}

Checking Android Permissions

async function checkPermissions(): Promise<{
foreground: boolean;
background: boolean;
notification: boolean;
}> {
if (Platform.OS !== 'android') {
return { foreground: true, background: true, notification: true };
}

const fine = await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
);
const coarse = await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION
);
const background =
Number(Platform.Version) >= 29
? await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION
)
: true;
const notification =
Number(Platform.Version) >= 33
? await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
)
: true;

return {
foreground: fine || coarse,
background,
notification,
};
}

Handling "Never Ask Again" (Android)

When a user selects "Don't ask again" on Android, you can no longer show the system permission dialog. Direct them to Settings:

const result = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
);

if (result === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
Alert.alert(
'Permission Required',
'Location permission was permanently denied. Please enable it in Settings.',
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'Open Settings', onPress: () => Linking.openSettings() },
]
);
}

The hook handles this automatically -- check permissionStatus.location.status === 'blocked' and canRequestAgain === false.


iOS Permission Model

iOS uses a two-tier location permission model and a separate notification permission.

Permission Flow

Step 1: WhenInUse Authorization
CLLocationManager.requestWhenInUseAuthorization()
User sees: "Allow While Using App" / "Don't Allow"
|
v
Step 2: Always Authorization (escalation)
CLLocationManager.requestAlwaysAuthorization()
User sees: "Change to Always Allow" / "Keep Only While Using"
|
v
Step 3: Notification Authorization
UNUserNotificationCenter.requestAuthorization()
User sees: "Allow Notifications" / "Don't Allow"

The useLocationPermissions hook handles all three steps when you call requestPermissions().

Location Permission Status Values

StatusCLAuthorizationStatusMeaning
undetermined.notDeterminedUser has not been asked yet
whenInUse.authorizedWhenInUseForeground only -- app can track while visible
granted.authorizedAlwaysFull background tracking support
denied.deniedUser explicitly denied
blocked.restrictedRestricted by parental controls or MDM

Notification Permission Status Values

StatusUNAuthorizationStatusMeaning
undetermined.notDeterminedUser has not been asked yet
granted.authorizedNotifications allowed
denied.deniedUser denied -- tracking still works

iOS Permission Behavior

Key differences from Android:

  • One-shot dialogs: iOS shows each permission dialog only once. If denied, the user must change the setting manually in Settings > Privacy & Security > Location Services
  • WhenInUse is partial: With whenInUse permission, tracking works while the app is in the foreground or recently backgrounded, but is not reliable for long-term background tracking
  • Always is required for background: For reliable background tracking, the user must grant "Always" permission
  • Notification is independent: Notification denial does not affect location tracking or geofence detection -- only visual notification display
  • No foreground notification: iOS uses a blue status bar indicator instead of a notification. All notificationOptions are silently ignored

Directing Users to Settings (iOS)

When permission is denied on iOS, direct the user to the app's settings page:

import { Linking, Platform } from 'react-native';

function openAppSettings() {
if (Platform.OS === 'ios') {
Linking.openURL('app-settings:');
} else {
Linking.openSettings();
}
}

Notification Permission

Notification permission is separate from location permission on both platforms:

PlatformWhen RequiredImpact of Denial
Android 13+ (API 33+)At runtimeForeground service notification may not appear, but tracking continues
Android < 13Auto-grantedN/A
iOSAt runtimeGeofence transition alerts will not display, but detection continues

The hook requests notification permission as the final step after location permission. Notification denial is non-blocking -- the return value of requestPermissions() only reflects location permission status.

To check notification permission separately:

const { permissionStatus } = useLocationPermissions();

if (!permissionStatus.notification.hasPermission) {
console.warn('Notifications disabled -- geofence alerts will not appear');
}

Permission Decision Tree

Use this to determine the minimum permissions your app needs:

Use CaseLocation PermissionBackgroundNotification
Foreground-only trackingForeground (fine/coarse)Not neededOptional
Background trackingForeground + BackgroundRequiredRecommended
Background tracking + geofence alertsForeground + BackgroundRequiredRequired for alerts

For foreground-only tracking, you can skip the background permission request entirely:

import { startTracking } from '@gabriel-sisjr/react-native-background-location';

// No background permission needed
const tripId = await startTracking({
foregroundOnly: true,
});

Google Play and App Store Compliance

Google Play (Android)

Before publishing to Google Play with background location:

  1. In-app disclosure: Show a prominent explanation before requesting background location
  2. Privacy policy: Disclose location data collection, background access, and data retention
  3. Play Console declarations: Complete Data Safety Form and Sensitive App Permissions form

See the Production guide for complete compliance details.

App Store (iOS)

Apple reviews location usage descriptions during App Review:

  1. All three NSLocation* Info.plist keys must be present and clearly written
  2. Privacy manifest must declare location data collection
  3. App must have a legitimate reason for "Always" authorization

See the iOS Setup for Info.plist requirements.

Next Steps