In an Expo app, why can expo-location report granted: true while a nearby-cafes screen still receives only approximate positions?
answer
- granted is not the whole story
- platform detail objects
- iOS accuracy full or reduced
- Android accuracy fine or coarse
- degrade the radius, not the feature
basics
~20 sexpo-location's granted flag means some foreground location access, not precise access. The user can allow only approximate location, which the response reports as ios.accuracy 'reduced' or android.accuracy 'coarse', so read those fields and widen the search instead.
solid answer
~40 s`getForegroundPermissionsAsync` and its request twin resolve to a `LocationPermissionResponse`: the shared `PermissionResponse` plus platform details. On iOS 14 and later the user can switch off Precise Location, and the response shows `ios.accuracy: 'reduced'` while `granted` stays true. On Android, Expo derives `status` and `granted` from the coarse location permission and sets `android.accuracy` to `'fine'` only when fine location is also granted, so an approximate-only grant reads as `granted: true` with `'coarse'`. A nearby-cafes screen should read the detail field, search a wider radius, label the results as approximate, and offer the Settings path to enable precise location, rather than treating approximate as denied or looping on requests.
code
tsx · 19 linesimport * as Location from 'expo-location';
export async function getCafeSearchArea() {
const perm = await Location.getForegroundPermissionsAsync();
if (!perm.granted) return null;
// granted can be true with only approximate access; read the platform detail.
const precise = perm.ios?.accuracy === 'full' || perm.android?.accuracy === 'fine';
const position = await Location.getCurrentPositionAsync({
accuracy: precise ? Location.Accuracy.High : Location.Accuracy.Balanced,
});
return {
coords: position.coords,
radiusMeters: precise ? 800 : 3000,
approximate: !precise,
};
}go deeper
Recall that users can share approximate location and that expo-location reports it in ios.accuracy and android.accuracy, not in granted.
Explain how each platform maps an approximate choice onto granted plus a detail field, including the coarse-based status on Android.
Show how you would detect the approximate grant, degrade the feature with a wider radius and a clear label, and offer a precise-location path without nagging.
Decide which features truly need precise or background location, since each extra grant costs acceptance, and design the rest to work well with approximate data.
## Granted means some access, not precise access Both major platforms let the user share an **approximate location** instead of a precise one. The shared Expo `PermissionResponse` (`status`, `granted`, `canAskAgain`, `expires`) was designed for yes-or-no capabilities, so it cannot express "yes, but only approximately". `expo-location` solves this by returning a **`LocationPermissionResponse`**, which extends `PermissionResponse` with two optional platform objects: | field | platform | values | meaning | |---|---|---|---| | `ios.scope` | iOS | `'whenInUse'`, `'always'`, `'none'` | when the app may use location | | `ios.accuracy` | iOS 14+ | `'full'`, `'reduced'` | precise or approximate; below iOS 14 always `'full'` | | `android.accuracy` | Android | `'fine'`, `'coarse'`, `'none'` | which location permission is held | ## How each platform arrives at granted plus approximate 1. **iOS.** Since iOS 14 the prompt carries a Precise switch, and the user can turn it off later in Settings. `granted` reflects the authorization (while in use or always), and the accuracy authorization is reported separately as `ios.accuracy`. A user who allowed location with Precise off yields `granted: true`, `ios.accuracy: 'reduced'`. 2. **Android.** Expo's Android module reads the coarse and fine location permissions separately. The response's `status`, `granted` and `canAskAgain` come from the **coarse** permission; `android.accuracy` becomes `'coarse'` when coarse is held and `'fine'` when fine is held too. A user who picked "Approximate" in the system dialog therefore yields `granted: true`, `android.accuracy: 'coarse'`. The consequence is the production bug in the question: the permission gate passes, positions arrive, and the nearby list is subtly wrong because every fix can be off by a large distance. ## What a nearby-cafes screen should do The graceful handling has three parts: - **Detect** the approximate grant from the detail field, not from `granted`. - **Degrade the feature, not the screen**: search a wider radius, sort by neighbourhood rather than exact walking distance, and label the list as approximate. - **Offer an upgrade path** in context, for example a "Use precise location" link that explains the benefit and calls `Linking.openSettings()`, where the user can turn precision on for this app. Two anti-patterns are common in code review: - **Treating approximate as denied** and hiding the feature, which punishes a reasonable privacy choice. - **Re-requesting in a loop** hoping for precision. Whether a second request can upgrade the grant is the platform's decision, and repeated prompts read as nagging. ## Where the check belongs Precision is a property of the grant at a moment in time, and the user can change it in Settings at any point, so: - **Check it at the gate**, where the screen decides what to render, next to `granted` and `canAskAgain`. - **Re-check when the screen regains focus**, with the get function, because a user who followed the Settings link may have switched Precise on. - **Pass the result down** rather than re-deriving it in every component, so the list, the map and the label agree. - **Ask for the accuracy you need** when reading positions: requesting the highest accuracy on an approximate grant does not produce precise fixes. ## Background location is a separate grant Precision is not the only dimension the shared fields hide. Background access has its own pair, `getBackgroundPermissionsAsync` and `requestBackgroundPermissionsAsync`, and requires the foreground permission first. On iOS, `ios.scope` shows whether the app holds `'whenInUse'` or `'always'`. A cafes screen needs only foreground access; asking for background access it does not use invites a denial. ## Web On the web neither detail object is present, so code must not assume `ios` or `android` exists. Use optional chaining and treat a missing detail as "precision unknown". ## Summary `granted` answers "may I use location at all?"; `ios.accuracy` and `android.accuracy` answer "how precise will it be?". A location feature that ignores the second question works on the developer's phone, where precise access was granted once, and quietly misbehaves for every user who chose approximate.
- On Android, why does expo-location's response say granted when only approximate location was allowed?Expo's Android module builds the response from the coarse location permission, so holding coarse access alone is a grant. It then sets `android.accuracy` to `'fine'` only when the fine permission is held too, which is where the approximate choice becomes visible.
- What does ios.scope add for a location feature?It says when the app may use location: `'whenInUse'` for foreground use, `'always'` when background access was granted, `'none'` otherwise. A foreground-only cafes screen needs `'whenInUse'`; checking scope matters once a feature, such as geofenced reminders, depends on background access.
saying these in an interview costs you the question
- granted: true guarantees precise location on both platforms.
- An approximate grant should be treated as a denial.
- Calling the request function repeatedly will eventually yield precise access.
- The ios and android detail objects are always present, even on the web.
- Background location is covered by the foreground permission.