To track a device's location in a React Native Android app in 2026, install a maintained geolocation library — react-native-geolocation-service or @react-native-community/geolocation for bare React Native, or expo-location for Expo apps — because the old built-in navigator.geolocation API was removed from React Native core. Declare ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION in android/app/src/main/AndroidManifest.xml, request them at runtime with PermissionsAndroid (Android 6.0+ requires it), then call getCurrentPosition() for a single fix or watchPosition() for continuous updates.
Key takeaways
- The legacy
navigator.geolocationglobal no longer ships with React Native core — older tutorials that call it without a library will silently fail. - For most apps, use
react-native-geolocation-service(accurate, FusedLocationProvider-based) or@react-native-community/geolocation; Expo apps useexpo-location. - Android needs both a manifest declaration and a runtime permission request — always handle the denied and "never ask again" cases.
- Use
getCurrentPosition()for a one-off location andwatchPosition()for live tracking, and always callclearWatch()on unmount to protect the battery. - Continuous background tracking needs
ACCESS_BACKGROUND_LOCATION, a foreground service (withforegroundServiceType="location"on Android 14+), and a Google Play policy declaration.
Why the old navigator.geolocation examples no longer work
Many older tutorials (this article included, before its 2026 rewrite) read location from a global navigator.geolocation object with no import:
// DEPRECATED — the built-in Geolocation API was removed from React Native core.
// On a current React Native version, navigator.geolocation is undefined.
navigator.geolocation.getCurrentPosition(
(position) => console.log(position.coords.latitude, position.coords.longitude),
(error) => console.log(error),
{ enableHighAccuracy: true, timeout: 30000 },
);React Native removed the built-in Geolocation web polyfill from core and extracted it into a separate, community-maintained package. On current versions, navigator.geolocation is undefined, so this code fails silently. The fix is to install one of the libraries below and import its API explicitly.
Which React Native location library should you use?
There are four mainstream options. Pick based on whether you use Expo and whether you need background tracking.
| Library | Foreground | Background | Maintenance | Expo support |
|---|---|---|---|---|
@react-native-community/geolocation |
Yes | Limited (no service helper) | Community-maintained (official core extraction) | Config plugin / dev build |
react-native-geolocation-service |
Yes (accurate, FusedLocation) | Limited | Lightly maintained | Dev build (prebuild) |
expo-location |
Yes | Yes (via TaskManager) | Actively maintained by Expo | First-class |
react-native-background-geolocation |
Yes | Robust (built-in foreground service) | Actively maintained | Plugin (paid Android release license) |
Rule of thumb: use expo-location if you are on Expo; use react-native-geolocation-service or @react-native-community/geolocation for foreground-only tracking in a bare app; and reach for react-native-background-geolocation only when you need rock-solid, battery-aware background tracking and can budget for its commercial Android license.
The examples below use react-native-geolocation-service because it is the most common choice for bare React Native Android apps, but the getCurrentPosition / watchPosition / clearWatch API is nearly identical in @react-native-community/geolocation.
Step 1 — Install a geolocation library
Add the package that matches your setup. For bare React Native on iOS, run pod install afterwards so the native module links.
# Bare React Native — accurate, FusedLocation-based
npm install react-native-geolocation-service
# ...or the official community package
npm install @react-native-community/geolocation
# Expo (managed workflow)
npx expo install expo-location
# iOS (bare RN): link native pods after installing
cd ios && pod install && cd ..Step 2 — Declare Android permissions in AndroidManifest.xml
Open android/app/src/main/AndroidManifest.xml and add the location permissions inside the <manifest> element, above <application>. ACCESS_FINE_LOCATION grants GPS-level precision; ACCESS_COARSE_LOCATION is the network/approximate fallback that some Android 12+ users choose instead. Only add the background and foreground-service permissions if you actually need them.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Precise (GPS) and approximate (network) location -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Only if you need location while the app is in the background (Android 10+) -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- Only for a continuous foreground service (Android 9+, typed on Android 14+) -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<application android:label="@string/app_name">
<!-- ...your activities and services... -->
</application>
</manifest>Google Play scrutinises ACCESS_BACKGROUND_LOCATION closely and will reject apps that request it without a clear, user-facing reason — so do not declare it "just in case."
Step 3 — Request runtime permission
A manifest entry alone is not enough on Android 6.0 (API 23) and above — you must ask the user at runtime. Use React Native's built-in PermissionsAndroid API. Always handle three outcomes: granted, denied, and "never ask again" (NEVER_ASK_AGAIN), where the only path forward is to send the user to the app's settings screen.
import { PermissionsAndroid, Platform } from 'react-native';
/**
* Request Android fine-location permission at runtime.
* @returns {Promise<boolean>} true if the user granted location access.
*/
export async function requestLocationPermission() {
if (Platform.OS !== 'android') return true; // iOS is handled via Info.plist + the library
const result = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: 'Location permission',
message: 'This app needs access to your location to track your position.',
buttonPositive: 'Allow',
buttonNegative: 'Deny',
},
);
// result is one of GRANTED | DENIED | NEVER_ASK_AGAIN
return result === PermissionsAndroid.RESULTS.GRANTED;
}getCurrentPosition vs watchPosition
getCurrentPosition(success, error, options)— resolves a single location fix. Use it for "find me once" features like autofilling an address.watchPosition(success, error, options)— returns a numericwatchIdand firessuccessevery time the device moves. Use it for live tracking (delivery, fitness, ride-hailing).clearWatch(watchId)— stops a watch. Always call it when the screen unmounts, or the GPS keeps draining the battery.
Common options:
| Option | What it does |
|---|---|
enableHighAccuracy |
true uses GPS for the most precise fix; false saves battery using network/Wi‑Fi. |
timeout |
Milliseconds to wait before the error callback fires. |
maximumAge |
Milliseconds a cached position may be reused before a fresh one is requested. |
distanceFilter |
Minimum metres the device must move before watchPosition emits again. |
interval / fastestInterval |
(Android) desired and fastest update intervals, in milliseconds. |
Step 4 — A working location-tracking component
The component below requests permission, takes one initial fix with getCurrentPosition, then starts a watchPosition subscription and posts each new coordinate to a backend endpoint. It is a function component using hooks and plain JavaScript (with JSDoc types — no TypeScript), and it cleans up the watcher on unmount.
import React, { useEffect, useRef, useState } from 'react';
import { View, Text } from 'react-native';
import Geolocation from 'react-native-geolocation-service';
import { requestLocationPermission } from './requestLocationPermission';
/**
* @typedef {Object} Coords
* @property {number} latitude
* @property {number} longitude
* @property {number} accuracy Accuracy radius in metres.
* @property {number} timestamp Epoch milliseconds of the fix.
*/
/**
* Live location tracker for Android (and iOS).
* Requests permission, watches position, and streams coordinates to the API.
*/
export default function LocationTracker() {
/** @type {[Coords | null, Function]} */
const [coords, setCoords] = useState(null);
const [error, setError] = useState('');
const watchId = useRef(/** @type {number | null} */ (null));
useEffect(() => {
let active = true;
(async () => {
const granted = await requestLocationPermission();
if (!granted) {
setError('Location permission denied.');
return;
}
if (!active) return;
// One-off initial fix.
Geolocation.getCurrentPosition(
(pos) => active && setCoords(toCoords(pos)),
(err) => active && setError(err.message),
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 },
);
// Continuous updates while the screen is mounted.
watchId.current = Geolocation.watchPosition(
(pos) => {
const next = toCoords(pos);
setCoords(next);
sendToBackend(next);
},
(err) => setError(err.message),
{ enableHighAccuracy: true, distanceFilter: 10, interval: 5000, fastestInterval: 2000 },
);
})();
// Cleanup: stop the GPS watcher to protect battery.
return () => {
active = false;
if (watchId.current != null) {
Geolocation.clearWatch(watchId.current);
watchId.current = null;
}
};
}, []);
return (
<View style={{ padding: 24 }}>
<Text>Latitude: {coords?.latitude ?? '—'}</Text>
<Text>Longitude: {coords?.longitude ?? '—'}</Text>
<Text>Accuracy: {coords ? `${coords.accuracy} m` : '—'}</Text>
{error ? <Text style={{ color: 'red' }}>{error}</Text> : null}
</View>
);
}
/**
* Normalise a library position object into a flat Coords record.
* @param {{ coords: { latitude: number, longitude: number, accuracy: number }, timestamp: number }} pos
* @returns {Coords}
*/
function toCoords(pos) {
return {
latitude: pos.coords.latitude,
longitude: pos.coords.longitude,
accuracy: pos.coords.accuracy,
timestamp: pos.timestamp,
};
}
/**
* POST a coordinate to your tracking API. Never crash the UI on a failed ping.
* @param {Coords} c
* @returns {Promise<void>}
*/
async function sendToBackend(c) {
try {
await fetch('https://api.example.com/v1/locations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(c),
});
} catch (e) {
// Queue offline and retry on the next fix.
console.warn('Failed to send location', e);
}
}Background location tracking on modern Android
watchPosition only runs reliably while your app is in the foreground. To keep tracking when the user switches apps or locks the screen, Android requires extra steps:
- Request
ACCESS_BACKGROUND_LOCATIONseparately. On Android 11+ (API 30) you cannot bundle it with the foreground request — you must first obtain foreground location, then send the user to system settings to pick "Allow all the time." - Run a foreground service. Continuous background location must run inside a foreground service with a persistent notification. On Android 14 (API 34) that service must declare
android:foregroundServiceType="location"and hold theFOREGROUND_SERVICE_LOCATIONpermission. - Pass Google Play review. Play's background location policy requires a written justification and a demo video; apps that request background location without a core, user-visible feature get rejected.
Do not hand-roll the service. Use expo-location with its TaskManager background task, or react-native-background-geolocation for a battery-optimised native foreground service. Note that foreground/background location services do not run in Expo Go on Android — you need a development build.
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
const LOCATION_TASK = 'background-location-task';
// Define the task at module scope (outside any component).
TaskManager.defineTask(LOCATION_TASK, ({ data, error }) => {
if (error) return;
const { locations } = /** @type {{ locations: Location.LocationObject[] }} */ (data);
// Send locations[0].coords to your backend here.
});
/**
* Ask for foreground then background permission, then start updates.
* @returns {Promise<void>}
*/
export async function startBackgroundTracking() {
const fg = await Location.requestForegroundPermissionsAsync();
if (fg.status !== 'granted') return;
const bg = await Location.requestBackgroundPermissionsAsync(); // user picks "Allow all the time"
if (bg.status !== 'granted') return;
await Location.startLocationUpdatesAsync(LOCATION_TASK, {
accuracy: Location.Accuracy.Balanced,
distanceInterval: 25, // metres
foregroundService: {
notificationTitle: 'Tracking location',
notificationBody: 'Your route is being recorded.',
},
});
}Battery, accuracy, and reliability tips
- Always
clearWatch()on unmount. A leaked GPS watcher is the number-one cause of battery complaints. - Tune
distanceFilterandinterval. Updating every 1 m / 1 s is rarely needed; 10–25 m every 5 s is plenty for most maps. - Drop
enableHighAccuracytofalsefor "city-level" features — network location is far cheaper than GPS. - Handle the no-fix case. Indoors, GPS can time out; show a fallback and retry rather than spinning forever.
- Test on a real device. The Android emulator's mock location does not reflect real GPS jitter or the live permission prompts.
Styling the screen that shows the map or coordinates? See our guide to building layouts with Flexbox in React Native. Still choosing your stack? Compare React Native vs Flutter and read how to pick a cross-platform framework, or see the well-known apps built with React Native.
Build location-aware features with confidence
MicroPyramid has shipped 50+ web and mobile products over 12+ years, including React Native apps with live tracking, geofencing, and map-heavy UIs. If you need GPS tracking, route playback, or background location done right — permissions, battery, and Play Store compliance included — our team can help with React Native and mobile app development.
Frequently Asked Questions
Why is navigator.geolocation undefined in React Native?
The built-in Geolocation web polyfill was removed from React Native core and moved to a separate package. On current versions, navigator.geolocation is no longer provided, so you must install and import a library such as react-native-geolocation-service, @react-native-community/geolocation, or expo-location.
Which is the best location library for a React Native Android app?
For Expo apps, use expo-location — it is first-class and supports background tracking. For bare React Native, react-native-geolocation-service gives accurate, FusedLocationProvider-based fixes, while @react-native-community/geolocation is the official core extraction. For heavy-duty background tracking, react-native-background-geolocation is the most robust (with a paid Android license).
Do I need to request location permission at runtime on Android?
Yes. Declaring ACCESS_FINE_LOCATION in AndroidManifest.xml is required but not sufficient on Android 6.0+ — you must also call PermissionsAndroid.request() (or your library's request method) at runtime and handle the granted, denied, and "never ask again" responses.
How do I track location in the background on Android?
You need the ACCESS_BACKGROUND_LOCATION permission (requested separately, with the user choosing "Allow all the time"), a foreground service with a persistent notification, and — on Android 14+ — foregroundServiceType="location". You must also justify background access during Google Play review. Libraries like expo-location and react-native-background-geolocation manage the service for you.
What is the difference between getCurrentPosition and watchPosition?
getCurrentPosition() returns a single location fix and is best for one-off lookups. watchPosition() returns a watchId and fires repeatedly as the device moves, which is what you want for live tracking. Always call clearWatch(watchId) when you no longer need updates so the GPS stops draining the battery.
How can I reduce battery drain from GPS tracking?
Clear the watcher on unmount, increase distanceFilter and the update interval, set enableHighAccuracy to false when approximate location is enough, and prefer balanced accuracy for background tasks. Avoid sub-second update intervals unless your feature genuinely requires them.