Not all who wander are lost
Expo Location is a library that allows you to access the geolocation of the device. In particular, it can access the location when the application runs in the background. It’s often used to create navigation applications (like Google Maps) or fitness tracking applications (e.g. Strava).
We’ll guide you through the steps required to set up and use the background location in an Expo app.
Setup
First of all, we need to install expo-location
, alongside with expo-task-manager
which we’ll use to keep the location running in a background task.
npx expo install expo-location expo-task-manager
The background location requires additional permission requests, which are configured in the Info.plist
for iOS and AndroidManifest.xml
for Android.
In an Expo project, you configure these settings in the app.json
file, in the section for additional configuration for plugins.
// app.json
// …
“plugins”: [
[
“expo-location”,
{
“isIosBackgroundLocationEnabled”: true,
“isAndroidBackgroundLocationEnabled”: true
}
]
],
// …
Permission request and task registering
Next, we need to ask the user to authorize location tracking. For background location, we need the “Always allow” option to be enabled.
This is done in two steps — ask for foreground location permission and then upgrade the permission to background location.
async function requestPermissions() {
const requestForeground = Location.requestForegroundPermissionsAsync;
const requestBackground = Location.requestBackgroundPermissionsAsync;
const foregroundRequest = await requestForeground();
if (foregroundRequest.granted) {
const backgroundRequest = await requestBackground();
if (backgroundRequest.granted) {
return true
}
}
return false
}
After doing that, we can register the task:
const LOCATION_TASK_NAME = “background-location-task”;
await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
accuracy: Location.Accuracy.Highest,
activityType: Location.LocationActivityType.Fitness,
});
We can customize many options, e.g. time after a batch of updates will be delivered to the callback, location accuracy, etc. You can learn about all the options from the Expo Location documentation [1], [2].
Lastly, we need to register a callback with the same name to be called when the background location update arrives. The callback must be fairly fast to preserve the battery and to fit in platform’s time constraints (e.g. on iOS background service must take at most 30s).
TaskManager.defineTask(LOCATION_TASK_NAME, saveLocationDataTask);
saveLocationDataTask
is a callback that takes one argument, an object with data
and error
keys. The data
has a following shape:
type FullLocationData = {
coords: {
altitude: number;
altitudeAccuracy: number;
latitude: number;
accuracy: number;
longitude: number;
heading: number;
speed: number;
};
timestamp: number;
};
In our example, we’re taking latitude, longitude, and accuracy and then saving it using async storage.
After adding mapbox and creating a visualisation…
function Map() {
const locations = useLocationData();
if (locations.length === 0) {
// …
}
return (
<MapView style={styles.fill} deselectAnnotationOnTap={true}>
<ShapeSource id="routeSource" shape={toRoute(locations)}>
<LineLayer id="routeFill" style={mapStyles.lineStyle} />
</ShapeSource>
<Camera defaultSettings={cameraConfig(locations[0])} />
</MapView>
);
}
function toRoute(locations: LocationData[]): LineString {
return { type: "LineString", coordinates: locations.map(toCoords) };
}
function cameraConfig(initialLocation: LocationData) {
return { centerCoordinate: toCoords(initialLocation), zoomLevel: 16 };
}
…we’re ready to supply the data. The easiest way is to use location emulation included in iOS simulator and Android emulator or using a fake GPS app on Android.
If you’re feeling adventurous, you can go outside for a walk.
Here’s a short route recorded around the Software Mansion office.
One thing to take note of is that some Android devices such as Huawei ones have strict battery policy — this means that even with background location enabled, the app may not receive updates. Based on this StackOverflow answer, it most likely can be circumvented by disabling battery optimisations for the app.