React Native Geolocation and Location Services Done Right
AI generated
RN
native
React Native · iOS · Android · Location
React Native Geolocation and Location Services Done Right
from permission dialogs to background tracking

A single getCurrentPosition call sounds like the easiest thing in the world, but location services sit at the intersection of accuracy, battery consumption and user privacy. Integrating geolocation into a React Native app without a clear concept risks drained batteries, confused users and rejected App Store reviews. This article shows how to request location services correctly on iOS and Android, track efficiently and test reliably.

19 min read Permissions · watchPosition · Geofencing iOS · Android · Expo

1. Why location services need special care

Geolocation in a React Native app sounds simple at first: call getCurrentPosition once, display the coordinates, done. In practice, location services sit at the intersection of three competing requirements: accuracy, battery consumption and user privacy. A GPS fix with high accuracy noticeably drains more power than a network-based position estimate, and every continuous background location query adds up to measurable battery loss over the course of a day.

Anyone who integrates geolocation without a clear concept usually notices too late: users complain about drained batteries, or the app fails App Store review because the permission justification is missing or background tracking is requested without a recognizable purpose. Both Apple and Google now actively check whether an app genuinely needs location access for its core purpose, and reject submissions where that connection is not clearly evident.

The following sections show how to implement location services cleanly in React Native: from choosing the right library, through correct permission requests, to background tracking and geofencing. The common thread is always the same: as much accuracy as the given use case requires, never more.

2. APIs at a glance: expo-location vs. react-native-geolocation-service

For geolocation in React Native there are essentially two actively maintained paths. In the Expo managed workflow, expo-location is the obvious choice: it wraps one-time position lookups, continuous tracking and geofencing behind a single, well-documented API, without requiring any manual native module linking.

In the bare workflow, meaning a React Native project without the Expo runtime, react-native-geolocation-service has established itself as the replacement for the geolocation web API polyfill that was eventually removed from React Native core. This community standard offers more fine-grained control over native Android and iOS providers and is actively tested against new OS versions, while the old core implementation had not received updates in years.

The choice between the two depends on the project setup, not on taste: if you already work with Expo, stick with expo-location, because pulling in additional bare libraries unnecessarily complicates the managed workflow. If you have an existing bare project or need very specific native control over GPS providers, react-native-geolocation-service is the better fit. Migrating away from the outdated, web-API-style navigator.geolocation to one of the two libraries is almost always the right call for location services in production apps.

3. Requesting permissions correctly

Location services may only become active on iOS and Android after explicit user consent, and both platforms require a visible justification for it. On iOS, the text for foreground access goes into NSLocationWhenInUseUsageDescription in Info.plist, and for background access additionally into NSLocationAlwaysAndWhenInUseUsageDescription. If either entry is missing, the app crashes on the access attempt instead of returning an error, a behavior that is easy to overlook during development because the crash only occurs on the very first access.

On Android, ACCESS_FINE_LOCATION for precise GPS positions and ACCESS_COARSE_LOCATION for network-based approximations are requested as runtime permissions, meaning at runtime through a dialog, not solely via the manifest declaration. Since Android 6, the manifest declaration is only the prerequisite; the actual consent is obtained via PermissionsAndroid.request() or the equivalent Expo API.

One detail that is frequently overlooked: since Android 10, ACCESS_BACKGROUND_LOCATION must be requested in a separate, second dialog and must not be bundled together with the foreground permissions in one request. Google rejects apps that try to obtain both at once, because the user should only grasp the implications of permanent location access after having already experienced the foreground access. In practice this means: request the foreground permission first, let the user actually use the app, and only ask for the second permission once a concrete background use case comes up.


// LocationTracker.js — request permission then watch position with distanceFilter
import { useEffect, useRef, useState } from 'react';
import { Platform, PermissionsAndroid } from 'react-native';
import Geolocation from 'react-native-geolocation-service';

async function requestLocationPermission() {
  if (Platform.OS === 'ios') {
    const authStatus = await Geolocation.requestAuthorization('whenInUse');
    return authStatus === 'granted';
  }

  const granted = await PermissionsAndroid.request(
    PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
    {
      title: 'Location access required',
      message: 'The app needs your location to track deliveries live.',
      buttonPositive: 'Allow',
    }
  );
  return granted === PermissionsAndroid.RESULTS.GRANTED;
}

export function useLiveLocation() {
  const [position, setPosition] = useState(null);
  const [error, setError] = useState(null);
  const watchId = useRef(null);

  useEffect(() => {
    let isMounted = true;

    requestLocationPermission().then((hasPermission) => {
      if (!hasPermission || !isMounted) return;

      watchId.current = Geolocation.watchPosition(
        (pos) => setPosition(pos.coords),
        (err) => setError(err),
        {
          accuracy: { android: 'high', ios: 'best' },
          distanceFilter: 25, // meters — throttle updates to save battery
          interval: 5000,
          fastestInterval: 2000,
        }
      );
    });

    return () => {
      isMounted = false;
      if (watchId.current !== null) {
        Geolocation.clearWatch(watchId.current);
      }
    };
  }, []);

  return { position, error };
}

4. One-time position lookups with getCurrentPosition

For a one-time position lookup, getCurrentPosition() is the right method, both in expo-location and in react-native-geolocation-service. The most important options are accuracy, timeout and maximumAge. accuracy controls how precise, and therefore how power-hungry, the position lookup turns out to be, timeout caps the wait time for a fix, and maximumAge defines how old a cached position may be before a new GPS fix is forced.

maximumAge is underused in practice. Many implementations force a fresh GPS fix on every call, even though a position cached ten or twenty seconds ago would be entirely sufficient for the given use case, for example showing the user's approximate position on a map right after app launch. A sensible default for maximumAge usually sits between 10000 and 30000 milliseconds, depending on how time-critical the position needs to be.

The timeout value should be generous enough to still get a fix under weak GPS reception, for example indoors or between tall buildings, but not so high that the UI feels frozen. Ten seconds is a common compromise. If the request fails, the UI should immediately show a clear error state instead of loading indefinitely, since a stuck loading indicator is one of the most common user annoyances with location services.

5. Continuous tracking with watchPosition

Once an application needs to react continuously to position changes, for example live tracking during a delivery run, watchPosition() comes into play. Unlike getCurrentPosition, watchPosition repeatedly delivers new positions for as long as the subscription is active, and must be explicitly stopped when leaving the relevant screen, otherwise tracking keeps running in the background and wastes battery unnecessarily.

The most important lever for battery optimization is distanceFilter. It specifies how many meters the position must have changed at minimum before a new update is triggered. A distanceFilter of 0 delivers every tiny change, which with a naturally noisy GPS signal leads to an unnecessarily large number of updates. A value between 10 and 50 meters drastically reduces the update frequency without compromising the practical usefulness of the tracking.

Accuracy and battery consumption are directly linked in watchPosition: accuracy: 'high' forces GPS and delivers the most precise position, but costs noticeably more power than a network-based estimate via cell towers or Wi-Fi access points. For many use cases, for example roughly showing the user's region for location-based content, a lower accuracy is entirely sufficient and should be chosen deliberately, rather than reflexively requesting the highest accuracy every time.

6. Background location tracking

Background location tracking, meaning location services that keep running even when the app is not in the foreground, requires the "Location updates" Background Modes capability in the Xcode project on iOS. Without this capability, iOS pauses every location query as soon as the app moves to the background, regardless of which permissions were granted beforehand.

Android has become significantly more restrictive since version 10, and even more so since version 12: continuous background tracking requires a foreground service with a permanently visible notification that makes it transparent to the user that the app is actively tracking their location. An attempt to run geolocation in the background without a foreground service is terminated by the system after a short time, regardless of granted permissions.

Two practical paths exist for implementation: react-native-background-geolocation as a complete, commercial solution with built-in foreground service management, batching and offline queueing, or the combination of expo-task-manager and expo-location for simpler use cases within the Expo ecosystem. The latter registers a task that gets woken up periodically by the operating system even when the app is closed, while the former is designed for continuous, high-frequency tracking as used by fitness or delivery apps.

7. Geofencing

Geofencing adds to location services the ability to react to entering or leaving a defined geographic region, without the app having to continuously evaluate the exact position itself. Both iOS and Android offer native region-monitoring APIs for this, which trigger enter and exit events that the operating system itself processes and only forwards to the app on an actual boundary crossing.

This system-side processing is considerably more efficient than periodically polling the position yourself and comparing it against stored coordinates, because the operating system handles the monitoring with adjusted accuracy and minimal energy consumption. Typical use cases are automatic check-in when entering a store, or geofenced notifications, for example a reminder as soon as the user is near a particular branch location.

One practical limit that surprises many developers: iOS monitors a maximum of around 20 regions simultaneously per app. If you need to cover more locations, for example every branch of a nationwide chain, only actively register the nearest 20 regions server-side based on the user's approximate position, and update that list dynamically on larger position changes, instead of trying to monitor every location at once.


{
  "expo": {
    "name": "mironsoft-demo",
    "plugins": [
      [
        "expo-location",
        {
          "locationAlwaysAndWhenInUsePermission": "This app uses your location to track deliveries live.",
          "locationWhenInUsePermission": "This app uses your location to show you nearby content.",
          "locationAlwaysPermission": "This app tracks deliveries even in the background.",
          "isAndroidBackgroundLocationEnabled": true,
          "isIosBackgroundLocationEnabled": true
        }
      ]
    ],
    "ios": {
      "infoPlist": {
        "UIBackgroundModes": ["location"]
      }
    },
    "android": {
      "permissions": [
        "ACCESS_FINE_LOCATION",
        "ACCESS_COARSE_LOCATION",
        "ACCESS_BACKGROUND_LOCATION"
      ]
    }
  }
}

8. Accuracy, error handling and testing

GPS signals are inherently inaccurate or entirely unavailable indoors, in underground parking garages or between tall buildings, because the signal from satellites gets disrupted by walls and reflections. A robust implementation of location services shows a clear state in such cases, for example "Location could not be determined", instead of silently falling back to a stale or obviously wrong position.

For testing on Android, it is worth looking at mock location detection in developer options: apps with security-critical location use cases should explicitly check whether a position originates from a mock location app, and reject it if necessary to prevent manipulation. For pure development testing, on the other hand, the position can be conveniently simulated in the Android emulator via Extended Controls, including playback of recorded GPS routes.

Under Xcode, locations are simulated via the location simulation in the scheme, either with a fixed coordinate or a predefined route such as a drive through a city. This allows testing watchPosition-based features without actually moving the device. Timeout and permission-denied errors should always be handled differently in the UI: a timeout justifies a retry button, whereas a denied permission warrants a message with a direct link to the system settings.


# Install the two most common React Native geolocation libraries
npm install react-native-geolocation-service
npx expo install expo-location

# iOS Simulator — set a fixed location
xcrun simctl location booted set 52.520008,13.404954

# iOS Simulator — play back a simulated route (walk/run/drive)
xcrun simctl location booted run --speed 20 route-berlin.gpx

# Android Emulator — set location via adb (requires emulator console access)
adb emu geo fix 13.404954 52.520008

# Android Emulator — connect to the console directly for scripted routes
telnet localhost 5554
# then inside the telnet session:
# geo fix 13.404954 52.520008

// LocationManager.swift — minimal background location updates
import CoreLocation

final class LocationManager: NSObject, CLLocationManagerDelegate {
    private let manager = CLLocationManager()

    override init() {
        super.init()
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
        manager.allowsBackgroundLocationUpdates = true
        manager.pausesLocationUpdatesAutomatically = false
    }

    func start() {
        manager.requestAlwaysAuthorization()
        manager.startUpdatingLocation()
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        // Forward the coordinate to the React Native bridge event emitter
        print("Background update: \(location.coordinate.latitude), \(location.coordinate.longitude)")
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("Location error: \(error.localizedDescription)")
    }
}

// LocationModule.kt — minimal FusedLocationProviderClient usage
import com.google.android.gms.location.*

class LocationModule(private val context: Context) {
    private val client = LocationServices.getFusedLocationProviderClient(context)

    private val request = LocationRequest.Builder(
        Priority.PRIORITY_HIGH_ACCURACY, 5000L
    ).setMinUpdateDistanceMeters(25f).build()

    private val callback = object : LocationCallback() {
        override fun onLocationResult(result: LocationResult) {
            val location = result.lastLocation ?: return
            // Forward coordinates to the React Native bridge event emitter
            println("Update: ${location.latitude}, ${location.longitude}")
        }
    }

    fun start() {
        client.requestLocationUpdates(request, callback, Looper.getMainLooper())
    }

    fun stop() {
        client.removeLocationUpdates(callback)
    }
}

9. Geolocation approaches compared

The choice between a one-time position lookup, continuous tracking and true background geolocation is not a matter of style, it depends directly on the use case. The following overview compares the three most common approaches for location services in React Native.

Approach Accuracy Battery usage Use case Complexity
getCurrentPosition (one-time) High (GPS) or low (network), depending on the option Low, single request One-time position lookup, e.g. on app start Low
watchPosition (continuous, foreground) High with accuracy: 'high', GPS-based Medium to high, depending on distanceFilter Live tracking during active app usage Medium, subscription management required
Background geolocation (library/foreground service) High, but batch-optimized High without careful tuning Tracking even with the app closed, e.g. delivery services High, foreground service + notification mandatory

In practice it is best to start with the simplest fitting approach: a one-time position lookup whenever it suffices, watchPosition only while the user is actively interacting with the app, and true background geolocation only when the app's core purpose strictly requires it. This order keeps both battery consumption and implementation effort for location services in check.

Mironsoft

React Native development for iOS and Android

Location services that respect battery and users?

We implement geolocation in your React Native app with correct permissions, battery-conscious tracking and robust background handling, including geofencing and reliable testing on real devices.

Permission flows

Requesting foreground and background permissions correctly and App Store compliant

Battery optimization

distanceFilter, accuracy tiers and foreground service tuning for minimal consumption

Geofencing & testing

Region monitoring, simulated GPS routes and device testing before every release

10. Summary

Location services and geolocation in React Native are not solved with a single API call, they require a deliberate decision per use case. expo-location fits the managed workflow, react-native-geolocation-service fits the bare workflow. Foreground and background permissions must be requested separately and at the right time, both on iOS via the Info.plist entries and on Android via the two-stage runtime dialogs.

getCurrentPosition with a sensible maximumAge covers most one-time lookups, watchPosition with a suitable distanceFilter covers live tracking during active use, and true background geolocation only when the app's core purpose strictly requires ongoing tracking, with a visible foreground service on Android. Geofencing replaces manual position polling with efficient, system-side region monitoring. Combining these building blocks to match your specific use case results in location services that work reliably without sacrificing battery life or user trust.

React Native Geolocation and Location Services: Key Takeaways

Right library

expo-location in the managed workflow, react-native-geolocation-service in the bare workflow instead of outdated web API polyfills.

Permissions in two steps

Foreground access first, ACCESS_BACKGROUND_LOCATION on Android 10+ only afterwards in a separate dialog.

Battery awareness

Actively use maximumAge and distanceFilter, choose accuracy only as high as the use case requires.

Background & geofencing

Foreground service with notification on Android, roughly 20 simultaneously monitored geofencing regions max on iOS.

11. FAQ: React Native Geolocation and Location Services

1expo-location or react-native-geolocation-service?
expo-location fits the managed workflow and covers one-time lookups, watchPosition and geofencing under one API. react-native-geolocation-service suits the bare workflow with a need for fine-grained control.
2Why a second dialog for background permission?
Since Android 10, ACCESS_BACKGROUND_LOCATION must be requested separately from foreground permissions so users understand the implications only after using the foreground access first.
3How to tune distanceFilter correctly?
10 to 50 meters is a good compromise. 0 meters delivers every tiny GPS fluctuation as an update and wastes battery.
4Accuracy vs. battery consumption?
accuracy: 'high' forces GPS and consumes noticeably more power than a network-based estimate. For rough location data, lower accuracy is often sufficient.
5How many geofencing regions at once?
iOS limits it to around 20 regions per app. With more locations, only actively register the nearest regions server-side.
6Simulate location without moving the device?
Xcode location simulation or xcrun simctl location for iOS, Extended Controls or adb emu geo fix for Android, including recorded routes.
7Handling a denied permission in the UI?
Distinguish timeout from denied permission: timeout gets a retry button, permission-denied gets a link to the system settings.
8Why is GPS inaccurate indoors?
Satellite signals get disrupted by walls, ceilings and reflections. Network-based location services via Wi-Fi often deliver more reliable values indoors.
9Why a foreground service on Android?
Since Android 10/12, background tracking must run via a foreground service with a visible notification so location access stays transparent to the user.
10watchPosition or polling with getCurrentPosition?
watchPosition is almost always better, since the operating system manages updates efficiently itself. Manual polling unnecessarily forces frequent fresh GPS fixes.