Created
June 28, 2024 20:42
-
-
Save tyrauber/dd16f76e78f7cc33386054051a00888f to your computer and use it in GitHub Desktop.
expo-location background tracking with WhenInUse
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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