skip to content

In a React Native Android app, how do you request the camera permission with PermissionsAndroid, and what can the request resolve to?

level: juniorimportance: must knowfreq 58%

answer

  1. dangerous permissions need a runtime prompt
  2. check() resolves a boolean
  3. request() resolves a status string
  4. granted, denied, never_ask_again
  5. Android only: iOS resolves 'denied'

basics

~10 s

Call 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 s

Camera 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 lines
typescript
import { 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

for a junior

Recall the three calls, check, request and requestMultiple, the three result strings, and that PermissionsAndroid works only on Android.

for a middle

Explain why check's boolean hides the permanent-refusal state, when the rationale dialog actually appears, and how requestMultiple reports mixed outcomes.

for a senior

Design the permission layer as a small state machine shared across screens, with per-platform branches and telemetry on which result users land on.

for a principal

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.