In an Expo SDK package such as expo-location, what is the difference between the get and request permission functions, and when do you call each?
answer
- one reads, one may prompt
- get never shows a dialog
- same PermissionResponse from both
- settled answer returns without a prompt
- ask at the moment of need
basics
~20 sgetForegroundPermissionsAsync only reads the current permission state and never shows a dialog; requestForegroundPermissionsAsync asks the system to prompt when it still may. Both resolve to the same PermissionResponse: check with get, request when the feature is used.
solid answer
~40 sEvery Expo SDK package that guards a capability exports a pair: a `get...PermissionsAsync` function that only reads the current state, and a `request...PermissionsAsync` function that asks the operating system to show its prompt. In expo-location the pair is `getForegroundPermissionsAsync` and `requestForegroundPermissionsAsync`. Both resolve to the same `PermissionResponse` (`status`, `granted`, `canAskAgain`, `expires`). The request function only shows a dialog while the answer is still open; if access is already granted, or the system will no longer prompt, it resolves straight away with that settled answer. So I call get on screen mount to decide what to render, and call request when the user taps the action that needs location, such as "Show cafes near me", never at app launch.
code
tsx · 31 linesimport * as Location from 'expo-location';
import { useEffect, useState } from 'react';
import { Button, Text, View } from 'react-native';
export function NearbyCafesEntry({ onReady }: { onReady: () => void }) {
const [status, setStatus] = useState<Location.PermissionStatus | null>(null);
useEffect(() => {
// Read only: never shows a dialog.
Location.getForegroundPermissionsAsync().then((res) => {
setStatus(res.status);
if (res.granted) onReady();
});
}, [onReady]);
const askForLocation = async () => {
// May show the system prompt, or resolve at once if already settled.
const res = await Location.requestForegroundPermissionsAsync();
setStatus(res.status);
if (res.granted) onReady();
};
if (status === null || status === Location.PermissionStatus.GRANTED) return null;
return (
<View>
<Text>Show cafes within walking distance of you?</Text>
<Button title="Use my location" onPress={askForLocation} />
</View>
);
}go deeper
Recall the pair: get reads the state and never prompts, request may show the system dialog, and both return the same PermissionResponse.
Explain when request resolves without a dialog, already granted or no longer askable, and why the call belongs on a user action rather than at launch.
Show how you would structure a screen around get-on-mount and request-on-action, and how you re-check after the user returns from the system settings.
Frame permission timing as product policy: which capabilities an app asks for, at which moment, and how a denial degrades the feature instead of blocking the app.
## The two functions every guarded Expo package exports Expo SDK packages that touch a protected capability (location, camera, microphone, media library, contacts and so on) all expose the same **pair of async functions**, named after the capability: - **`get<Capability>PermissionsAsync()`** reads the current permission state from the operating system. It **never shows a dialog** and has no side effect beyond the read. - **`request<Capability>PermissionsAsync()`** asks the operating system to show its **system permission prompt**, waits for the user's answer and resolves with the result. In `expo-location` the foreground pair is `getForegroundPermissionsAsync` and `requestForegroundPermissionsAsync`; in `expo-camera` it is `getCameraPermissionsAsync` and `requestCameraPermissionsAsync`. The naming is a convention, not a coincidence: each package builds its pair on shared plumbing in `expo-modules-core`, which is why the shape carries over from one package to the next. ## Both resolve to the same object Both functions resolve to a **`PermissionResponse`** (a package may extend it; `expo-location` adds `ios` and `android` detail objects): | field | type | meaning | |---|---|---| | `status` | `'granted' \| 'denied' \| 'undetermined'` | the state as an enum, `PermissionStatus` | | `granted` | `boolean` | convenience flag, true only when `status` is `'granted'` | | `canAskAgain` | `boolean` | whether a request can still show the system prompt | | `expires` | `'never' \| number` | when the grant expires; today every grant reports `'never'` | Because both return the same shape, the code that reads the result does not care which function produced it. ## What a request does when the answer is already settled A request is **not a guaranteed dialog**. The system only shows its prompt while the answer is still open: 1. **Undetermined** (never asked): the prompt appears and the promise resolves with the user's choice. 2. **Already granted**: no dialog; the promise resolves with the granted response. 3. **Denied and no longer askable** (`canAskAgain: false`): no dialog; the promise resolves with the denied response. On iOS a single denial settles it; on Android the system can allow another prompt after a first denial and stops after the user blocks it. This is why "just call request every time" is a bug in disguise: on a settled denial it silently does nothing visible, and the user sees a button that appears broken. ## When to call each The pattern interviewers expect for a nearby-cafes screen: 1. **On mount, call get** (or let a permission hook do it) to decide what to render: the cafe list, an explanation card with an "Allow location" button, or a "turn it on in Settings" card. 2. **On the user's action, call request.** The best moment is when the user taps something that obviously needs location, because the prompt then arrives with context and is more likely to be accepted. 3. **Re-check with get** when you have reason to believe the state changed outside the app, for example after sending the user to the system settings. Two anti-patterns are worth naming: - **Requesting at app launch** for every capability the app might use. The user has no context, is likely to deny, and on iOS that single denial means the app can no longer prompt at all. - **Using request as a status check.** It can show a dialog, so it is the wrong tool for deciding what to render. ## Expo Go and builds The runtime functions are only half of the contract; the other half is native configuration: - **Expo Go** is a prebuilt app with its own usage descriptions, so get and request work there without any configuration of yours. - **Development and store builds** need the usage descriptions in the native project, set through each package's config plugin options; without them the request cannot show a proper prompt. - **The web** exposes the same functions for packages that support it, but browsers only allow capabilities such as location and camera from a secure context (`https://` or `http://localhost`). ## Summary | | get function | request function | |---|---|---| | Shows a system dialog | never | only while the answer is open | | Typical caller | mount logic, re-checks | a user action that needs the capability | | Result | `PermissionResponse` | `PermissionResponse` | | Safe to call repeatedly | yes | yes, but it is not a status check | The short version: **get to decide, request to ask**, and ask at the moment the user reaches for the feature.
- Why is calling the request function on every render or every mount a bad idea even though it is safe?It turns a status check into a possible dialog, so the prompt fires without context, often at app launch, where users tend to deny. On iOS that single denial is final, and on later calls the request resolves silently as denied, so the screen looks broken. Reading with get and requesting on a user action avoids both problems.
- Does expo-location's foreground pair also cover background location?No. Background access has its own pair, `getBackgroundPermissionsAsync` and `requestBackgroundPermissionsAsync`, and the foreground permission must already be granted before a background request can succeed. On Android 11 or later the background request sends the user to a system settings page rather than showing an inline dialog, so the app should explain why before calling it.
saying these in an interview costs you the question
- The get function shows the permission prompt if nothing has been decided yet.
- Calling the request function always shows the system dialog.
- Request every permission at app launch so later screens can assume a grant.
- Use the request function as the way to check the current status.
- A denied request can be retried until the user says yes.