skip to content

Runtime Permissions

Android asks at runtime via PermissionsAndroid; iOS needs a usage-description string before any prompt, and users can deny for good. Interviewers probe asking in context and the settings fallback.

part ofReact Nativeoverview, primer and where to startread it →
on this pageshow

explore

questions

4

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.
open as a page

Why does a React Native iOS app crash when it first opens the camera if Info.plist lacks NSCameraUsageDescription, and how do you fix it?

level: middleimportance: should knowfreq 46%

basics

~20 s

iOS requires a purpose string for each protected resource before it shows a prompt; without NSCameraUsageDescription it terminates the app on camera access. Add a specific description to Info.plist, or ios.infoPlist in Expo, and ship a new binary.

open as a page

In React Native, what does PermissionsAndroid's never_ask_again result mean, and how should a document-scanning app recover from it?

level: middleimportance: should knowfreq 44%

basics

~10 s

never_ask_again means Android will no longer show the prompt, so request() resolves instantly; the scanner should explain why the camera is needed, offer Linking.openSettings(), and re-check the permission when the user returns.

open as a page

How should a React Native document-scanning app time and sequence its camera and photo-library permission requests so users rarely deny them for good?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Ask for each permission only when the user starts the feature that needs it, explain first in your own UI, request one permission per action, and avoid asking at all where the system photo picker can do the job.

open as a page