In a React Native Android app, how do you request the camera permission with PermissionsAndroid, and what can the request resolve to?
answer
- dangerous permissions need a runtime prompt
- check() resolves a boolean
- request() resolves a status string
- granted, denied, never_ask_again
- Android only: iOS resolves 'denied'
basics
~10 sCall PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA) after declaring CAMERA in the manifest; it resolves to 'granted', 'denied' or 'never_ask_again', while check() only reports whether the permission is already held.
solid answer
~40 sCamera is a **dangerous** permission, so declaring it in `AndroidManifest.xml` is not enough: the app must ask at runtime. React Native's `PermissionsAndroid` wraps that model. `check(permission)` resolves a **boolean** and never prompts; `request(permission, rationale?)` shows the system dialog and resolves `RESULTS.GRANTED` (`'granted'`), `RESULTS.DENIED` (`'denied'`) or `RESULTS.NEVER_ASK_AGAIN` (`'never_ask_again'`). If the permission is already held, `request` resolves `'granted'` without a dialog. `requestMultiple` asks for several at once and resolves an object mapping each permission to its status. The module is Android-only: on iOS each method logs a warning and resolves a negative value (`false`, `'denied'`, or an empty object), so iOS needs a different path.
code
typescript · 31 linesimport { PermissionsAndroid } from 'react-native';
export type CameraAccess = 'granted' | 'denied' | 'blocked';
// Android only: on iOS the camera library asks through its own API.
export async function requestCameraAndroid(): Promise<CameraAccess> {
const alreadyGranted = await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.CAMERA,
);
if (alreadyGranted) {
return 'granted';
}
const result = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.CAMERA,
{
title: 'Camera access',
message: 'The scanner uses the camera to photograph your documents.',
buttonPositive: 'Continue',
},
);
switch (result) {
case PermissionsAndroid.RESULTS.GRANTED:
return 'granted';
case PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN:
return 'blocked';
default:
return 'denied';
}
}go deeper
Recall the three calls, check, request and requestMultiple, the three result strings, and that PermissionsAndroid works only on Android.
Explain why check's boolean hides the permanent-refusal state, when the rationale dialog actually appears, and how requestMultiple reports mixed outcomes.
Design the permission layer as a small state machine shared across screens, with per-platform branches and telemetry on which result users land on.
Decide whether a shared permissions abstraction or library is worth owning across apps, weighing platform drift against duplicated per-screen logic.
## Why a runtime request exists Android splits permissions into **normal** ones, granted at install when listed in `AndroidManifest.xml`, and **dangerous** ones — camera, microphone, location, contacts, media — that also need the user's consent while the app runs. The React Native docs describe `PermissionsAndroid` as the module for exactly those dangerous permissions. Declaring the permission in the manifest is still required; the runtime request only asks the user for something the manifest already names. ## The PermissionsAndroid surface | Member | What it does | Resolves to | |---|---|---| | `check(permission)` | asks whether the app holds the permission now; never prompts | `true` or `false` | | `request(permission, rationale?)` | shows the system dialog, optionally preceded by a rationale | a status string | | `requestMultiple(permissions)` | one system flow for several permissions | `{ [permission]: status }` | | `PERMISSIONS.CAMERA` and friends | constants holding the full names, such as `'android.permission.CAMERA'` | — | | `RESULTS` | `GRANTED`, `DENIED`, `NEVER_ASK_AGAIN` | — | The older `checkPermission` and `requestPermission` methods are deprecated and log a warning pointing to `check` and `request`. ## The three results - **`'granted'`** — the user allowed it, or the app already held it; `request` short-circuits without a dialog in that case. - **`'denied'`** — the user refused, but Android would still show the prompt next time. - **`'never_ask_again'`** — the user refused and the system will no longer show the prompt, so further `request` calls resolve immediately with no dialog. The only way back is the app's page in the system Settings. `check` cannot tell "never asked" from "permanently refused": both are `false`. That is why a flow uses `check` to decide what to render and `request` to learn the actual status. ## A request, step by step 1. Declare `android.permission.CAMERA` in the manifest. 2. When the user taps **Scan document**, call `check`; if it is `true`, open the camera. 3. Otherwise call `request` with the camera constant and, optionally, a rationale. 4. Branch on the result: open the camera, show an inline explanation with a retry, or show a Settings link. ## The rationale argument The optional second argument has a required `title` and `message` and optional `buttonPositive`, `buttonNegative` and `buttonNeutral`. It is not shown on every call. `request` first asks the OS whether a rationale is appropriate, which normally becomes true only after the user has denied once, and only then shows the dialog before the system prompt. On a first request, the system prompt appears directly. ## Picking the constant for the Android version Some permissions changed across Android releases, and `PermissionsAndroid.PERMISSIONS` carries both generations: - `READ_MEDIA_IMAGES`, with `READ_MEDIA_VIDEO` and `READ_MEDIA_AUDIO`, for Android 13 (API level 33) and later; - `READ_EXTERNAL_STORAGE` for older versions; - `READ_MEDIA_VISUAL_USER_SELECTED` for the partial photo access a user can grant on Android 14 and later. On Android, `Platform.Version` is the API level as a number, so a branch such as `Platform.Version >= 33` picks the constant. Requesting the old storage permission on a new device typically resolves as a refusal without a useful prompt, which then shows up in logs as if the user had said no. ## On iOS `PermissionsAndroid` is Android-only. On iOS, `check` resolves `false`, `request` resolves `'denied'` and `requestMultiple` resolves `{}`, each with a console warning. React Native core has no iOS permission API: the system prompt appears when a native module first asks for access — a camera library's own request function or a permissions library — and it requires a usage-description string in `Info.plist`. Code shared between platforms therefore branches on `Platform.OS`. ## Common mistakes - Treating any result other than `'granted'` as the same case, which hides the permanent-refusal state. - Forgetting the manifest entry and wondering why the prompt never appears. - Calling `request` on app start instead of at the moment the feature is used. - Assuming `check` returning `false` means the user can still be prompted. ## Reading the result in production Log which of the three results users land on, per permission and per screen. A high share of `'never_ask_again'` on the camera means the ask comes too early or without context, which is a design problem no retry logic fixes.
- Why does PermissionsAndroid.request resolve 'never_ask_again' at once, with no dialog, for a permission the app forgot to declare in AndroidManifest.xml?Android refuses a runtime request for a permission missing from the manifest without showing a prompt. React Native then asks the OS whether a rationale should be shown; for a refusal the user never saw, it is not, so the module reports `'never_ask_again'`. The fix is the manifest entry, not a Settings link.
- When would you use requestMultiple instead of two request calls in a React Native Android app?When two permissions serve one action the user just started, such as recording a video that needs camera and microphone. `requestMultiple` runs one system flow and resolves an object keyed by permission, so you must handle a mixed outcome. It accepts no rationale argument, so explain the need in your own UI first.
saying these in an interview costs you the question
- Listing CAMERA in the manifest is enough; no runtime request is needed.
- check() tells you whether the user can still be prompted.
- PermissionsAndroid also shows the iOS permission prompt.
- Any result other than granted can be handled the same way.
- The rationale dialog appears on every request call.