Skip to main content

Platform Comparison: Android vs iOS

Side-by-side comparison of how @gabriel-sisjr/react-native-background-location works on Android and iOS. The JavaScript API is identical on both platforms, but the underlying native implementations differ significantly.

Feature Matrix

FeatureAndroidiOS
Background location trackingForeground Service + FusedLocationProviderCLLocationManager + background entitlement
Local persistenceRoom Database (SQLite)Core Data (SQLite)
Permission flow3-step (foreground, background, notification)3-step (When In Use, Always, Notification)
Background mechanismForeground Service (must show notification)Background Location Updates (blue bar indicator)
Crash recoveryWorkManager (Android 12+) / direct restartRecoveryManager + Significant Location Monitoring
User-visible indicatorPersistent notification (fully customizable)Blue status bar / Dynamic Island pill (not customizable)
Distance filterSupportedSupported
Foreground-only modeSupportedSupported
Real-time JS eventsNativeEventEmitter via SharedFlow singletonsNativeEventEmitter via closure callbacks

Location Tracking

AspectAndroidiOS
Location providerFusedLocationProvider (Google Play Services) or AndroidLocationProvider (fallback)CLLocationManager
Activity typeNot applicable.automotiveNavigation
Distance filterVia FusedLocationProvider setSmallestDisplacementVia CLLocationManager.distanceFilter
Pauses automaticallyNot applicablepausesLocationUpdatesAutomatically = false (disabled)
Stale location filterNot applicable (FusedLocationProvider handles this)Rejects locations older than 10 seconds
Invalid location filterNot applicableRejects locations with horizontalAccuracy < 0

Accuracy Mapping

The LocationAccuracy enum maps to different native values on each platform:

LocationAccuracyAndroid (Priority)iOS (CLLocationAccuracy)
HIGH_ACCURACYPRIORITY_HIGH_ACCURACYkCLLocationAccuracyBest (~5-10m)
BALANCED_POWER_ACCURACYPRIORITY_BALANCED_POWER_ACCURACYkCLLocationAccuracyHundredMeters (~100m)
LOW_POWERPRIORITY_LOW_POWERkCLLocationAccuracyKilometer (~1km)
NO_POWERPRIORITY_PASSIVEkCLLocationAccuracyThreeKilometers (~3km)
PASSIVEPRIORITY_PASSIVEkCLLocationAccuracyThreeKilometers (~3km)

On iOS, NO_POWER and PASSIVE accuracy levels use startMonitoringSignificantLocationChanges() instead of startUpdatingLocation(), providing cell-tower-based updates with minimal power consumption.

Default Distance Filters (When Not Explicitly Set)

AccuracyAndroidiOS
HIGH_ACCURACYNone (all updates)None (all updates)
BALANCED_POWER_ACCURACYNone50 meters
LOW_POWERNone200 meters
NO_POWER / PASSIVENoneNone (significant changes only)

Permissions

Permission Flow

Android (3-step for Android 13+):

Step 1: ACCESS_FINE_LOCATION + ACCESS_COARSE_LOCATION
|
v
Step 2: ACCESS_BACKGROUND_LOCATION (Android 10+, separate dialog)
|
v
Step 3: POST_NOTIFICATIONS (Android 13+, for foreground service notification)

iOS (3-step):

Step 1: requestWhenInUseAuthorization()
|
v (if foregroundOnly = false)
Step 2: requestAlwaysAuthorization()
|
v
Step 3: UNUserNotificationCenter.requestAuthorization() (notification permission)

Permission Status Mapping

LocationPermissionStatusAndroidiOS
UNDETERMINEDNever requestednotDetermined
WHEN_IN_USENot applicable (Android does not distinguish)authorizedWhenInUse
GRANTEDAll permissions grantedauthorizedAlways
DENIEDUser denied (can ask again)denied
BLOCKEDUser selected "Never ask again"restricted (parental controls / MDM)

Notification Permission Status

The useLocationPermissions hook also tracks notification permission status on both platforms via the NotificationPermissionStatus enum:

NotificationPermissionStatusAndroidiOS
GRANTEDPOST_NOTIFICATIONS granted (Android 13+)UNAuthorizationStatus.authorized
DENIEDPOST_NOTIFICATIONS deniedUNAuthorizationStatus.denied
UNDETERMINEDNever requested (or Android < 13)UNAuthorizationStatus.notDetermined

Permission State Structure

The permissionStatus returned by useLocationPermissions uses a granular nested structure:

interface PermissionState {
hasAllPermissions: boolean; // true when both location AND notification are granted
location: {
hasPermission: boolean; // true when location permission is fully granted
status: LocationPermissionStatus;
canRequestAgain: boolean;
};
notification: {
hasPermission: boolean; // true when notification permission is granted
status: NotificationPermissionStatus;
canRequestAgain: boolean;
};
}
PropertyDescription
hasAllPermissionstrue only when both location and notification are granted
location.hasPermissiontrue when location permission matches the requested level
location.statusCurrent LocationPermissionStatus value
location.canRequestAgainfalse if user blocked location permanently
notification.hasPermissiontrue when notification permission is granted
notification.statusCurrent NotificationPermissionStatus value
notification.canRequestAgainfalse if user blocked notifications permanently

Permission Methods

MethodAndroid BehavioriOS Behavior
checkLocationPermission()Checks native permission status via PermissionsAndroidChecks CLLocationManager.authorizationStatus
requestLocationPermission(false)Requests foreground + background + notificationRequests WhenInUse, then Always, then Notification
requestLocationPermission(true)Requests foreground onlyRequests WhenInUse only

Persistence

AspectAndroidiOS
Database technologyRoom (SQLite abstraction)Core Data (SQLite abstraction)
Write strategyBatched async writes (batch size 10, 5s timeout)Batched async writes (batch size 10, 5s timeout)
Location entity fieldsid, tripId, latitude, longitude, timestamp, accuracy, altitude, speed, bearing, verticalAccuracy, speedAccuracy, bearingAccuracy, provider, isFromMockProviderIdentical schema
Tracking state persistenceRoom entity (TrackingStateEntity)Core Data entity (TrackingStateEntity)
Schema migrationRoom migration-safe versioningCore Data lightweight migration
Stop token storageSharedPreferencesUserDefaults
Recovery counter storageSharedPreferencesUserDefaults

Background Mode

AspectAndroidiOS
MechanismForeground Service with FOREGROUND_SERVICE_LOCATION typeBackground Location Updates capability
Start requirementMust call startForeground() within 5-10s (Android 12+)Must set allowsBackgroundLocationUpdates = true
User indicatorPersistent notificationBlue status bar / Dynamic Island indicator
Indicator customizationFull control (title, text, icon, color, actions)None (system-managed blue bar)
Service timeoutAndroid 15+ has time limits (handled by library restart)No timeout (location entitlement keeps app alive)
App killed by userService usually continuesApp terminates; no automatic recovery
App killed by systemService continues independentlyApp terminates; significant location can re-launch

Geofencing

AspectAndroidiOS
Native APIGeofencingClient (Google Play Services)CLLocationManager region monitoring
Detection modelPassive -- piggybacks on other apps' location requestsActive -- OS monitors regions independently
GPS keepalive (heartbeat)Automatic FusedLocationProviderClient heartbeat (15-min, BALANCED_POWER_ACCURACY) when tracking is not activeNot needed (region monitoring is active)
Heartbeat battery impact~2-4%/dayN/A
Notification responsivenesssetNotificationResponsiveness(5000) (~5s delivery)System-managed
Maximum geofences10020 (shared with iBeacon regions)
DWELL detectionNative (GeofencingClient loitering)Software-based timer
Persistence after rebootBootCompletedReceiver re-registers geofences + restarts heartbeatCLLocationManager persists regions across restarts
Persistence after force-quitSurvives (geofences stored in Room DB)Survives (regions managed by OS)

Crash Recovery

AspectAndroidiOS
Recovery mechanismWorkManager (Android 12+) or direct service restartRecoveryManager + Significant Location Monitoring
Wake-up triggerWorkManager scheduled taskSignificant location change event (cell tower)
Re-launch capabilityService restarts within same processiOS re-launches the entire app in background
Stop tokenSharedPreferences, 60-second validityUserDefaults, 60-second validity
Restart loop limit5 restarts per hour5 recoveries per hour
Recovery after rebootService does not restart after rebootSignificant location monitoring survives reboot
User force-quitService may survive (manufacturer-dependent)No recovery (Apple enforces user intent)

Notifications and Indicators

AspectAndroidiOS
Notification requiredYes (foreground service must show notification)No notification concept
Notification titleCustomizable via notificationOptions.titleNot applicable
Notification textCustomizable via notificationOptions.textNot applicable
Notification iconCustomizable via notificationOptions.smallIconNot applicable
Notification colorCustomizable via notificationOptions.colorNot applicable
Notification actionsUp to 3 action buttons via notificationOptions.actionsNot applicable
Notification channelCustomizable via notificationOptions.channelIdNot applicable
Update notificationupdateNotification(title, text)No-op (returns successfully)
Background indicatorNotification in shadeBlue status bar / Dynamic Island

Cross-platform tip: You can pass notificationOptions on iOS without error. The values are parsed and stored but have no visible effect on the notification UI. This means you can use the same TrackingOptions object on both platforms without platform-specific branching.

TrackingOptions: Platform Applicability

OptionAndroidiOSNotes
accuracyAppliedAppliedDifferent native mappings (see Accuracy table)
updateIntervalAppliedStored onlyiOS does not support interval-based updates; uses distance filter
fastestIntervalAppliedIgnoredAndroid-only FusedLocationProvider feature
maxWaitTimeAppliedIgnoredAndroid-only batching parameter
waitForAccurateLocationAppliedStored onlyiOS relies on desiredAccuracy instead
distanceFilterAppliedAppliedBoth platforms support minimum distance between updates
foregroundOnlyAppliedAppliedControls allowsBackgroundLocationUpdates on iOS
notificationOptionsAppliedStored onlyUnified notification config object; no visible effect on iOS
.titleAppliedIgnoredForeground service notification title
.textAppliedIgnoredForeground service notification body text
.smallIconAppliedIgnoredAndroid drawable resource name for the small icon
.largeIconAppliedIgnoredAndroid drawable resource name for the large icon
.colorAppliedIgnoredHex color string for notification accent color
.showTimestampAppliedIgnoredWhether to show timestamp on the notification
.subtextAppliedIgnoredSubtext displayed below the notification content
.channelIdAppliedIgnoredAndroid notification channel ID
.channelNameAppliedIgnoredAndroid notification channel name
.priorityAppliedIgnoredNotification priority (NotificationPriority enum)
.actionsAppliedIgnoredUp to 3 action buttons on the notification
onUpdateIntervalAppliedAppliedJS-level throttling, works on both platforms

Events

Both platforms emit the same events through NativeEventEmitter with the same data format.

Location Update Event

FieldAndroidiOSNotes
tripIdPresentPresentSame value
latitudeStringStringSame format
longitudeStringStringSame format
timestampMilliseconds since epochMilliseconds since epochSame format
accuracyHorizontal accuracy (meters)Horizontal accuracy (meters)Same meaning
altitudeMeters above sea levelMeters above sea levelSame meaning
speedMeters per secondMeters per secondSame meaning
bearingDegrees (0-360)Degrees (0-360, from course)Same meaning
verticalAccuracyMetersAvailable (API 26+)AvailableSame meaning
speedAccuracyMetersPerSecondAvailable (API 26+)AvailableSame meaning
bearingAccuracyDegreesAvailable (API 26+)Available (from courseAccuracy)Same meaning
elapsedRealtimeNanosAvailableNot availableAndroid-only
provider"gps", "network", "passive""gps" or "simulated"Different values
isFromMockProviderAvailable (API 18+)Available (from sourceInformation)Same meaning

Warning Events

Warning TypeAndroidiOS
SERVICE_TIMEOUTEmitted on Android 15+ service timeoutNot emitted (no service concept)
TASK_REMOVEDEmitted when app is swiped from recentsNot emitted
LOCATION_UNAVAILABLEEmitted when GPS signal lostEmitted when CLLocationManager fails
LOCATION_UPDATES_PAUSEDNot emittedEmitted when iOS pauses updates for battery
LOCATION_UPDATES_RESUMEDNot emittedEmitted when iOS resumes paused updates
PERMISSION_REVOKEDNot emitted natively (handled by hooks)Emitted when permission is revoked mid-session
PERMISSION_DOWNGRADEDNot applicableEmitted when Always is downgraded to WhenInUse

Store Compliance

RequirementGoogle Play (Android)App Store (iOS)
Compliance guideGoogle Play ComplianceApp Store Compliance
In-app disclosureRequired (blocking dialog before permission)Not required (but recommended for good UX)
Usage descriptionsNot applicableRequired (Info.plist keys)
Privacy policyRequired (must mention location)Required (must mention location)
Data safety / nutrition labelsPlay Console Data Safety formApp Store Connect Privacy labels
Permission justificationPermissions declaration form in Play ConsoleReview notes + justification text
Demo videoRequired for background locationNot required (but helpful for review)
Privacy manifestNot applicableRequired (PrivacyInfo.xcprivacy)

Testing Differences

ScenarioAndroidiOS
Simulated locationadb shell commands, mock location appsXcode scheme GPX files, Simulator menu
Background testingWorks reliably on emulatorWorks on Simulator, but significant location does not
Device testingRequired for manufacturer-specific battery issuesRequired for accurate battery and GPS behavior
Crash recoveryCan simulate via adb shell am force-stopCan simulate via Xcode stop, but not force-quit
Permission resetSettings > Apps > PermissionsSettings > Privacy > Location Services
Battery simulationadb shell dumpsys deviceidle force-idleNo equivalent (must test on device)

Cross-Platform Code Pattern

The library is designed so you can use the same code on both platforms:

import {
LocationAccuracy,
useLocationPermissions,
useBackgroundLocation,
useLocationUpdates,
} from '@gabriel-sisjr/react-native-background-location';

function TrackingScreen() {
const { permissionStatus, requestPermissions } = useLocationPermissions();
const { isTracking, tripId, startTracking, stopTracking } = useBackgroundLocation();

useLocationUpdates({
onLocationUpdate: (location) => {
// Same data format on both platforms
console.log(location.latitude, location.longitude);
},
onLocationWarning: (warning) => {
// Handle platform-specific warnings gracefully
console.log(warning.type, warning.message);
},
});

const handleStart = async () => {
if (!permissionStatus.hasAllPermissions) {
await requestPermissions();
}

// Same options work on both platforms
// notificationOptions are silently ignored on iOS
await startTracking({
accuracy: LocationAccuracy.HIGH_ACCURACY,
distanceFilter: 50,
notificationOptions: {
title: 'Tracking Active', // Android only, ignored on iOS
text: 'Recording your route', // Android only, ignored on iOS
},
});
};

return (
<View>
<Button title="Start" onPress={handleStart} />
<Button title="Stop" onPress={stopTracking} />
</View>
);
}

See Also