1. Reading Location with expo-location
npx expo install expo-location
import * as Location from 'expo-location';
async function getCurrentLocation() {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') return null;
const location = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.Balanced,
});
return location.coords; // { latitude, longitude, altitude, accuracy, ... }
}
useEffect(() => {
let subscription: Location.LocationSubscription | undefined;
(async () => {
subscription = await Location.watchPositionAsync(
{ accuracy: Location.Accuracy.Balanced, timeInterval: 5000, distanceInterval: 10 },
(loc) => setCoords(loc.coords)
);
})();
return () => subscription?.remove();
}, []);
Accuracy.Balanced is the right default for most UI — Highest
drains battery noticeably faster and is worth reserving for the moments accuracy
genuinely matters, like turn-by-turn navigation.
2. Motion Sensors with expo-sensors
npx expo install expo-sensors
import { Accelerometer } from 'expo-sensors';
useEffect(() => {
Accelerometer.setUpdateInterval(100); // ms
const subscription = Accelerometer.addListener(({ x, y, z }) => {
const magnitude = Math.sqrt(x * x + y * y + z * z);
if (magnitude > 2.5) {
onShakeDetected();
}
});
return () => subscription.remove();
}, []);
expo-sensors exposes the same listener pattern for
Gyroscope, Magnetometer and Barometer — each
is a stream of readings you subscribe to and must remember to unsubscribe from, or
it keeps firing (and draining battery) after the screen unmounts.
3. Requesting Permissions Properly
Both platforms show the OS permission dialog exactly once per reason given
unless the user changes it manually in Settings — so the moment you call
requestForegroundPermissionsAsync() for the first time is the only
real shot at a "yes." Show your own explanation screen first.
function LocationRationale({ onContinue }: { onContinue: () => void }) {
return (
<View>
<Text>We use your location to show nearby stores.</Text>
<Text>We never share it, and you can turn it off anytime in Settings.</Text>
<Pressable onPress={onContinue}>
<Text>Continue</Text>
</Pressable>
</View>
);
}
Foreground permission (requestForegroundPermissionsAsync)
covers location reads while the app is open and visible. Background
location (requestBackgroundPermissionsAsync) is a separate, stricter
request — both platforms require the foreground permission to already be granted
first, and Apple in particular reviews background location use closely; only ask
for it if the feature genuinely needs to track location while the app isn't open.
4. Hands-on Exercise
Build a "find my spot" screen with a shake-to-refresh gesture
Combine location and motion sensors into one small, permission-respectful screen.
Requirements:
- A rationale screen shown before the first location permission request, explaining why the app wants it.
- A screen showing live latitude/longitude, updating via
watchPositionAsync, cleaned up correctly on unmount. - An accelerometer listener that detects a shake gesture (magnitude over a threshold) and re-centers/refreshes the displayed location when triggered.
- A visible permission-denied state that doesn't crash or show blank coordinates if the user declines.
- Both subscriptions (location and accelerometer) removed in a cleanup function so they stop firing when the screen unmounts.
If the shake gesture fires constantly (or never), the accelerometer's update interval and your magnitude threshold need tuning together — a 100ms interval with a threshold around 2.5–3.0 on Math.sqrt(x*x+y*y+z*z) is a reasonable starting point on a real device (the simulator/emulator won't produce real motion data).
5. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
Why request location permission behind your own rationale screen rather than calling requestForegroundPermissionsAsync() the moment a screen mounts?
Why request location permission behind your own rationale screen rather than calling requestForegroundPermissionsAsync() the moment a screen mounts?
The OS dialog only shows once per permission unless the user manually resets it in Settings — so an unexplained prompt on first load, with no context, is likely to get a reflexive "Don't Allow" that's then hard to recover from. A brief explanation first gives the user a reason to say yes on the one attempt that matters.
Q2
Why does background location require a separate permission request from foreground location, on both platforms?
Why does background location require a separate permission request from foreground location, on both platforms?
Background location — the app reading position while not open or visible — is more sensitive and more heavily scrutinized (especially by Apple's App Store review), so both platforms gate it behind its own explicit request, on top of foreground permission already being granted. It should only be requested when a feature genuinely needs it.
Q3
What happens if an accelerometer listener isn't removed when its screen unmounts?
What happens if an accelerometer listener isn't removed when its screen unmounts?
The listener keeps firing in the background, continuing to consume CPU and battery for a screen the user can no longer see, and can trigger stale callbacks (like onShakeDetected) referencing state or navigation from an unmounted component. The cleanup function returned from useEffect exists specifically to call subscription.remove() and prevent this.
Q4
Why is Accuracy.Balanced a better default than Accuracy.Highest for most location UI?
Why is Accuracy.Balanced a better default than Accuracy.Highest for most location UI?
Highest accuracy uses more power-hungry positioning methods and updates more aggressively, which measurably drains battery faster for a benefit most UI (showing a city or neighborhood, nearby results) doesn't need. Reserve Highest for features where precision genuinely matters, like turn-by-turn navigation.