As the title says, I’m having issues with background location tracking for iOS.
I am currently using Expo’s internal development build to run the app on my physical iPhone device. When the app is in the foreground, the background task runs as expected and my backend recieves the users location. However, when I exit the app the location updates stop.
Is the issue with my code, or is it with the Expo development build?
import { useEffect, useState, useCallback } from "react";
import * as Location from "expo-location";
import * as SecureStore from "expo-secure-store";
import * as TaskManager from "expo-task-manager";
import axios from "axios";
import { handleError } from "@/utils/handleError";
const LOCATION_TASK_NAME = "background-location-task";
TaskManager.defineTask(LOCATION_TASK_NAME, async ({ data, error }) => {
if (error) {
handleError(error, "Background location task error");
return;
}
console.log("Location task triggered");
if (data) {
const { locations } = data as any;
const [location] = locations;
const { coords } = location;
console.log("Location updated", coords);
const accessToken = await SecureStore.getItemAsync("accessToken");
const uid = await SecureStore.getItemAsync("uid");
if (accessToken && uid) {
try {
await axios.post(
`${process.env.EXPO_PUBLIC_API_ENDPOINT}/users/${uid}/position`,
{
latitude: coords.latitude,
longitude: coords.longitude,
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
);
} catch (error) {
handleError(error, "Failed to update your position.");
}
}
}
});
export const useLocation = () => {
const [errorMsg, setErrorMsg] = useState<string | null>(null);
const [foregroundStatus, requestForegroundPermission] =
Location.useForegroundPermissions();
const [backgroundStatus, requestBackgroundPermission] =
Location.useBackgroundPermissions();
const requestPermissions = useCallback(async () => {
const foregroundStatusResult = await requestForegroundPermission();
if (!foregroundStatusResult.granted) {
setErrorMsg("Permission to access location in the foreground was denied");
return {
foregroundStatus: foregroundStatusResult.status,
backgroundStatus: null,
};
}
const backgroundStatusResult = await requestBackgroundPermission();
if (!backgroundStatusResult.granted) {
setErrorMsg("Permission to access location in the background was denied");
}
return {
foregroundStatus: foregroundStatusResult.status,
backgroundStatus: backgroundStatusResult.status,
};
}, [requestForegroundPermission, requestBackgroundPermission]);
const checkPermissions = useCallback(async () => {
const foregroundStatusResult =
await Location.getForegroundPermissionsAsync();
const backgroundStatusResult =
await Location.getBackgroundPermissionsAsync();
return {
foregroundStatus: foregroundStatusResult.status,
backgroundStatus: backgroundStatusResult.status,
};
}, []);
const startTracking = useCallback(async () => {
if (!backgroundStatus?.granted) {
setErrorMsg("Permission to access location was denied");
return;
}
console.log("Starting location tracking");
await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
accuracy: Location.Accuracy.High,
timeInterval: 10000, // Update every 10 seconds
distanceInterval: 10, // in meters
foregroundService: {
notificationTitle: "Location Tracking",
notificationBody: "We are tracking your location in the background",
notificationColor: "#FF0000",
},
});
}, [backgroundStatus]);
const stopTracking = useCallback(async () => {
console.log("Stopping location tracking");
await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME);
}, []);
/*
useEffect(() => {
return () => {
stopTracking();
};
}, [stopTracking]);
*/
return {
errorMsg,
foregroundStatus,
backgroundStatus,
requestPermissions,
checkPermissions,
startTracking,
stopTracking,
permissionStatus: backgroundStatus?.status,
};
};