Skip to content

Instantly share code, notes, and snippets.

@tyrauber
Created June 28, 2024 20:42
Show Gist options
  • Save tyrauber/dd16f76e78f7cc33386054051a00888f to your computer and use it in GitHub Desktop.
Save tyrauber/dd16f76e78f7cc33386054051a00888f to your computer and use it in GitHub Desktop.
expo-location background tracking with WhenInUse
// Contrary to what the expo-location documentation says:
// > To use Background Location methods, the following requirements apply:
// > Location permissions must be granted. On iOS it must be granted with Always option.
// It is possible to use expo-location to follow location updates in the background or from the lock screen, with the WhenInIUse permission.
// The trick is to only request foreground permissions, but use `startLocationUpdatesAsync` and `expo-task-manager`.
// This context demonstrates how:
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
import React, { useState, useEffect } from 'react';
const distanceInterval = 1;
const timeInterval = 300;
const LOCATION_TASK_NAME = 'LOCATION_TASK_NAME';
let foregroundSubscription = null;
/* Location Service Pub/Sub */
const Service = () => {
let subscribers = [];
return {
subscribe: (sub) => subscribers.push(sub),
setLocation: (location) => {
subscribers.forEach((sub) => {
//console.log(JSON.stringify({ location }));
return sub(location);
});
},
unsubscribe: (sub) => {
subscribers = subscribers.filter((_sub) => _sub !== sub);
},
};
};
export const LocationService = Service();
// Define the background task for location tracking
TaskManager.defineTask(LOCATION_TASK_NAME, async ({ data, error }) => {
if (error) {
console.error(error);
return;
}
if (data) {
// Extract location coordinates from data
const { locations } = data;
const location = locations[0];
if (location) {
//console.log("Location in background", location.coords)
LocationService.setLocation(location);
}
}
});
export const LocationContext = React.createContext({});
export const LocationProvider = ({ children, enable }) => {
const [location, setLocation] = useState<object>({});
const [recording, setRecording] = useState<boolean>(false);
const [foreground, setForeground] = useState<object>({});
const [background, setBackground] = useState<object>({});
const requestPermissions = async () => {
let status = await Location.requestForegroundPermissionsAsync();
if (status?.status === 'denied') {
console.log(status.status === 'denied');
Location.enableNetworkProviderAsync();
}
setForeground(status);
};
const startForegroundUpdate = async () => {
if (foreground?.status !== 'granted') return;
//console.log('Starting Foreground Update');
foregroundSubscription?.remove();
foregroundSubscription = await Location.watchPositionAsync(
{
accuracy: Location.Accuracy.BestForNavigation,
distanceInterval,
},
(location) => {
LocationService.setLocation(location);
}
);
};
const stopForegroundUpdate = () => {
foregroundSubscription?.remove();
};
const startBackgroundUpdate = async () => {
// if (background?.status !== 'granted') return;
const isTaskDefined = await TaskManager.isTaskDefined(LOCATION_TASK_NAME);
if (!isTaskDefined) return;
// Don't track if it is already running in background
const hasStarted = await Location.hasStartedLocationUpdatesAsync(LOCATION_TASK_NAME);
if (hasStarted) {
console.log('Already started');
return;
}
await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
// For better logs, we set the accuracy to the most sensitive option
accuracy: Location.Accuracy.BestForNavigation,
distanceInterval,
timeInterval,
// Make sure to enable this notification if you want to consistently track in the background
showsBackgroundLocationIndicator: true,
foregroundService: {
notificationTitle: 'Location',
notificationBody: 'Location tracking in background',
notificationColor: '#fff',
},
});
};
// Stop location tracking in background
const stopBackgroundUpdate = async () => {
const hasStarted = await Location.hasStartedLocationUpdatesAsync(LOCATION_TASK_NAME);
if (hasStarted) {
await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME);
}
};
const startRecording = async () => {
startForegroundUpdate();
startBackgroundUpdate();
setRecording(true);
};
const stopRecording = () => {
stopForegroundUpdate();
stopBackgroundUpdate();
setRecording(false);
setLocation({});
};
useEffect(() => {
LocationService.subscribe(setLocation);
return () => {
LocationService.unsubscribe(setLocation);
};
}, []);
return (
<LocationContext.Provider
value={{
location,
foreground,
background,
setForeground,
setBackground,
requestPermissions,
startForegroundUpdate,
stopForegroundUpdate,
startBackgroundUpdate,
stopBackgroundUpdate,
startRecording,
stopRecording,
}}
>
{children}
</LocationContext.Provider>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment