The most popular RN geolocation library is https://github.com/react-native-geolocation/react-native-geolocation, and it supports this quite easily. I prefer this library over others because it automatically handles asking for permissions and such, and seems to have the simplest API.
Just do this:
Geolocation.watchPosition((position)=>{
const {latitude, longitude} = position.coords;
// Do something.
})
This requires no additional setup other than including the background modes fetch
and location
, and also the appropriate usage descriptions.
I find this more usable than Expo's API because it doesn't require any weird top level code and also doesn't require me to do anything other than create a watch position handler, which is really nice.
EDIT 2023!:
These days I would highly recommend using Expo's library instead of any of the other community libraries (mainly because our app started crashing when android got an OS update b/c of the lib I was using).
In fact, if you have to choose between expo and non expo library, always choose the expo library if only for the stability. Setting up expo's background location watching isn't super well documented but here's what I did to get it working in our app:
import { useEffect, useRef } from "react";
import * as Location from "expo-location";
import { LatLng } from "react-native-maps";
import * as TaskManager from "expo-task-manager";
import { LocationObject } from "expo-location";
import { v4 } from "uuid";
type Callback = (coords: LatLng) => void;
const BACKGROUND_TASK_NAME = "background";
const executor: (body: TaskManager.TaskManagerTaskBody<object>) => void = (
body
) => {
const data = body.data as unknown as { locations: LocationObject[] };
const l = data?.locations[0];
if (!l) return;
for (const callback of Object.values(locationCallbacks)) {
callback({
latitude: l.coords.latitude,
longitude: l.coords.longitude,
});
}
};
TaskManager.defineTask(BACKGROUND_TASK_NAME, executor);
const locationCallbacks: { [key: string]: Callback } = {};
const hasStartedBackgroundTaskRef = {
hasStarted: false,
};
function startBackgroundTaskIfNecessary() {
if (hasStartedBackgroundTaskRef.hasStarted) return;
Location.startLocationUpdatesAsync(BACKGROUND_TASK_NAME, {
accuracy: Location.Accuracy.Balanced,
}).catch((e) => {
hasStartedBackgroundTaskRef.hasStarted = false;
});
hasStartedBackgroundTaskRef.hasStarted = true;
}
function addLocationCallback(callback: Callback) {
const id = v4() as string;
locationCallbacks[id] = callback;
return {
remove: () => {
delete locationCallbacks[id];
},
};
}
export default function useLocationChangeListener(
callback: Callback | null,
active: boolean = true
) {
const callbackRef = useRef<null | Callback>(callback);
callbackRef.current = callback;
useEffect(() => {
if (!active) return;
if (!callback) return;
Location.getLastKnownPositionAsync().then((l) => {
if (l)
callback({
latitude: l.coords.latitude,
longitude: l.coords.longitude,
});
});
startBackgroundTaskIfNecessary();
const watch = Location.watchPositionAsync({}, (location) => {
callback({
latitude: location.coords.latitude,
longitude: location.coords.longitude,
});
});
const subscription = addLocationCallback(callback);
return () => {
subscription.remove();
watch.then((e) => {
e.remove();
});
};
}, [callback, active]);
useEffect(() => {
if (__DEV__) {
addLocationCallback((coords) => {
console.log("Location changed to ");
console.log(coords);
});
}
}, []);
}
You need to ask for background location permissions before this, BTW. Follow expos guide.
It's pretty risky trusting community libraries for stuff like this because of the fact that breaking android OS updates can happen at any moment and with open source maintainers they may or may not stay on top of it (you can more or less trust expo too, though)
you can use it in your standalone build
. TaskManager is not available forbare
react native apps, the docs say – Baseline