Request Runtime Permissions in Android (Kotlin Guide)

Request Runtime Permissions in Android (Kotlin Guide)


If you are building your first Android app, learning how to request runtime permissions in Android is essential. Since Android 6.0 (Marshmallow), users grant certain “dangerous” permissions while the app is running—not just from the Play Store install screen. In this step by step guide to request runtime permissions in Android, you’ll learn what runtime permissions are, when to ask for them, and how to implement the request flow using modern Android APIs with Kotlin and Jetpack Compose. We’ll also include Java examples for classic Views, and cover Android 12–14 changes like granular media permissions, selected-photos access, and the notifications permission.

What are runtime permissions in Android?

Android runtime permission dialog for camera access on a phone, demonstrating how to request runtime permissions in Android
What the system permission prompt looks like during a runtime request.

Android permissions come in two main types:

  • Normal permissions: Low-risk. Granted automatically at install time. Example: SET_ALARM, INTERNET.
  • Dangerous permissions: Higher-risk access to user data or device features. Must be granted by the user at runtime. Example: CAMERA, READ_CONTACTS, location, microphone, and—on newer Android—granular media and notifications.

Declaring a dangerous permission in AndroidManifest.xml isn’t enough; you must also check it at runtime and, if not granted, request it with a system dialog. For an android runtime permissions tutorial to be future-proof, use the Activity Result APIs (instead of the old requestPermissions() callback) and ask only when the user triggers an action that needs it.

Docs: Permissions overview – developer.android.com/guide/topics/permissions/overview

When should you request a permission?

Flowchart of the Android runtime permission lifecycle from check to rationale, prompt, grant or deny, and app settings.
Permission request lifecycle from check to grant/deny and redirect to Settings.

Follow these best practices:

  • Only request at the moment of need (e.g., when a user taps “Take Photo”). This boosts acceptance and trust.
  • Explain why you need it if the user already denied once. Use shouldShowRequestPermissionRationale to show a rationale UI.
  • Don’t loop prompts. If the user denies with “Don’t ask again,” guide them to App Settings instead of re-requesting in a loop.
  • Prefer safer alternatives when possible: Use the system Photo Picker instead of READ access to storage. It requires no storage permission and gives users control. It’s built-in on Android 13+ and available on many Android 11–12 devices via Google Play system updates. Fallback to the Storage Access Framework on older devices.
  • Handle new permission types correctly: notifications (Android 13+), granular media permissions (Android 13+), selected-photos access (Android 14), and nearby devices (Bluetooth/Wi‑Fi) changes.

Practical checklist: how to request runtime permissions in Android (beginner-friendly)

  • Add only the permissions you truly need to AndroidManifest.xml.
  • Trigger the request from a user action (e.g., “Take Photo”, “Start Recording”).
  • Check current status first with ContextCompat.checkSelfPermission.
  • If previously denied, show a short rationale explaining value and privacy.
  • Use Activity Result APIs to request; do not use deprecated requestPermissions().
  • If “Don’t ask again” is selected, show a Settings shortcut and keep limited features usable.
  • Prefer Photo Picker over broad storage access on Android 13+; SAF on older devices.
  • Respect version changes: notifications (13+), granular media (13+), selected-photos (14), nearby devices (12+/13+).
  • Test on API 23–34 devices/emulators to verify flows, especially deny and partial-access paths.

Add required permissions in the Manifest

Declare only what you truly need. Some examples:

XML
<manifest ...>

<!-- Camera (dangerous) -->
<uses-permission android:name="android.permission.CAMERA" />

<!-- Notifications (Android 13+, runtime) -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<!-- Granular media (Android 13+) replace READ_EXTERNAL_STORAGE -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

<!-- Android 14: detect/handle selected-photos access -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />

<!-- Nearby devices (Android 12+) for Bluetooth -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

<!-- Wi‑Fi (Android 13+) without location -->
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />

<!-- Location (only if truly needed, and for pre-13 compatibility) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<!-- Background location requires staged flow and Settings redirect -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

</manifest>

Note: Do not request location just to scan Bluetooth or Wi‑Fi on Android 12+/13+ if your use case doesn’t derive location. Use the nearby-device permissions instead.

Quick reference: common runtime permissions and safer alternatives

Permission(s) Purpose Ask when… Android 12–14 notes Safer alternative
CAMERA Capture photos/videos User taps “Take Photo/Scan” One-time option available; handle “Don’t ask again” None
RECORD_AUDIO Voice input/recording Start of recording/voice feature One-time option possible None
ACCESS_COARSE/FINE_LOCATION Map, nearby content, navigation When showing location features Prefer coarse if precise not needed Allow address search/manual input
ACCESS_BACKGROUND_LOCATION Geofencing/tracking in background Only after foreground granted; via Settings Staged request flow required Use foreground-only if acceptable
READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIO (13+)
Pre-13: READ_EXTERNAL_STORAGE
Browse user media When opening gallery browser Android 14 may grant selected-photos only Use system Photo Picker
READ_MEDIA_VISUAL_USER_SELECTED (14+) Detect selected-photos access Post-grant checks/routing Indicates partial access Offer re-selection or Settings
POST_NOTIFICATIONS (13+) Show notifications Right before first meaningful notification Don’t request at first launch In-app toasts/badges if denied
BLUETOOTH_SCAN, BLUETOOTH_CONNECT, BLUETOOTH_ADVERTISE (12+)
BLE scan/connect When starting scan/pairing Don’t request location unless required Use QR pairing/user-initiated flows
NEARBY_WIFI_DEVICES (13+) Manage nearby Wi‑Fi devices When connecting to device network No location needed in many cases Use system Wi‑Fi picker intents

Step-by-step: Request a single permission in Kotlin (Jetpack Compose)

This example shows how to check and request camera permission in Android using the modern Activity Result API from a Compose UI. The flow:

  1. User taps a button to take a photo.
  2. We check CAMERA.
  3. If not granted, we show rationale (when appropriate) and then ask.
  4. Handle granted/denied results, including “Don’t ask again.”
Kotlin
Code
class="cd-package">import android.Manifest
class="cd-package">import android.app.Activity
class="cd-package">import android.content.Intent
class="cd-package">import android.net.Uri
class="cd-package">import android.os.Build
class="cd-package">import android.provider.Settings
class="cd-package">import androidx.activity.compose.rememberLauncherForActivityResult
class="cd-package">import androidx.activity.result.contract.ActivityResultContracts
class="cd-package">import androidx.compose.material3.*
class="cd-package">import androidx.compose.runtime.*
class="cd-package">import androidx.compose.ui.platform.LocalContext
class="cd-package">import androidx.core.app.ActivityCompat
class="cd-package">import androidx.core.content.ContextCompat

class="cd-annotation">@Composable
fun CameraPermissionButton(
    onPermissionGranted: () -> Unit
) {
    val context = LocalContext.current
    val activity = context as Activity

    var showRationale by remember { mutableStateOf(false) }
    var showSettingsDialog by remember { mutableStateOf(false) }

    val requestPermissionLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.RequestPermission()
    ) { isGranted: Boolean ->
        if (isGranted) {
            onPermissionGranted()
        } else {
            // If the user denied and checked "Don't ask again___CDPHSTR0___Don't ask again___CDPHSTR1___Camera permission needed___CDPHSTR2___We need access to your camera to take photos. Please grant the permission.___CDPHSTR3___Continue___CDPHSTR4___Cancel___CDPHSTR5___Enable permission in Settings___CDPHSTR6___Camera permission is permanently denied. Open App Settings to allow it.___CDPHSTR7___package___CDPHSTR8___Open Settings___CDPHSTR9___Not now___CDPHSTR10___Don't ask again___CDPHSTR11___Take Photo")
    }
}

This is a clean android permission request example with Compose. The key is tying the request to a real user action to improve acceptance.

Handle “Don’t ask again” (never ask again)

When a permission result is denied and shouldShowRequestPermissionRationale() returns false, the user likely selected “Don’t ask again.” Present a friendly message and direct them to Settings. You can detect this case both in the result callback and on subsequent checks. Avoid repeatedly calling the system dialog—Android will ignore it and the UX suffers.

Request multiple permissions (Android 13+ media and notifications)

On Android 13+, storage access is split into granular permissions. For example, to browse images and videos (without audio), use READ_MEDIA_IMAGES and READ_MEDIA_VIDEO. Android 13+ also adds the POST_NOTIFICATIONS runtime permission. Here’s a request permission Android Kotlin example that requests multiple permissions at once, with version checks and a recommendation to use the Photo Picker where possible.

Kotlin
Code
class="cd-package">import android.Manifest
class="cd-package">import android.os.Build
class="cd-package">import androidx.activity.compose.rememberLauncherForActivityResult
class="cd-package">import androidx.activity.result.contract.ActivityResultContracts
class="cd-package">import androidx.compose.material3.*
class="cd-package">import androidx.compose.runtime.*
class="cd-package">import androidx.compose.ui.platform.LocalContext
class="cd-package">import androidx.core.content.ContextCompat

class="cd-annotation">@Composable
fun MediaAndNotificationPermissions(
    onReady: () -> Unit
) {
    val context = LocalContext.current

    val neededPermissions = remember {
        val perms = mutableListOf<String>()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            perms += Manifest.permission.READ_MEDIA_IMAGES
            perms += Manifest.permission.READ_MEDIA_VIDEO
            // Ask only if you will actually post notifications soon:
            perms += Manifest.permission.POST_NOTIFICATIONS
        } else {
            // Pre-13: READ_EXTERNAL_STORAGE if truly required (avoid broad access)
            perms += android.Manifest.permission.READ_EXTERNAL_STORAGE
        }
        perms.toTypedArray()
    }

    val launcher = rememberLauncherForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { results ->
        val allGranted = results.values.all { it }
        if (allGranted) onReady()
        else {
            // Consider showing rationale or falling back to Photo Picker:
            // Photo Picker requires no storage permission.
        }
    }

    Button(onClick = {
        // Version-adaptive check
        val allGranted = neededPermissions.all { perm ->
            ContextCompat.checkSelfPermission(
                context, perm
            ) == android.content.pm.PackageManager.PERMISSION_GRANTED
        }
        if (allGranted) {
            onReady()
        } else {
            launcher.launch(neededPermissions)
        }
    }) { Text("Browse Media and Notify") }
}

Tip: For most photo/video selection use cases, use the system Photo Picker, which does not require storage permissions and respects user privacy. It’s available on Android 13+ and on many Android 11–12 devices via Google Play system updates. For older devices, use ACTION_OPEN_DOCUMENT from the Storage Access Framework.

Java example (classic Views) using Activity Result APIs

If you’re following a request permission Android Java flow in an Activity with XML layouts, use registerForActivityResult too:

Java
Code
class="cd-keyword cd-access">public class CameraActivity extends AppCompatActivity {

    class="cd-keyword cd-access">private ActivityResultLauncher<String> requestPermissionLauncher;

    class="cd-annotation">@Override
    class="cd-keyword cd-access">protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);

        requestPermissionLauncher =
            registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
                if (isGranted) {
                    openCamera();
                } else {
                    boolean showRationale = ActivityCompat.shouldShowRequestPermissionRationale(
                            this, Manifest.permission.CAMERA);
                    if (!showRationale) {
                        // "Don't ask again"
                        openAppSettings();
                    } else {
                        showRationaleDialog();
                    }
                }
            });

        findViewById(R.id.btn_take_photo).setOnClickListener(v -> checkAndRequestCamera());
    }

    class="cd-keyword cd-access">private void checkAndRequestCamera() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                == PackageManager.PERMISSION_GRANTED) {
            openCamera();
        } else {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
                showRationaleDialog();
            } else {
                requestPermissionLauncher.launch(Manifest.permission.CAMERA);
            }
        }
    }

    class="cd-keyword cd-access">private void showRationaleDialog() {
        new AlertDialog.Builder(this)
                .setTitle("Camera permission needed")
                .setMessage("We need camera access to take photos in this app.")
                .setPositiveButton("Continue", (d, w) -> 
                        requestPermissionLauncher.launch(Manifest.permission.CAMERA))
                .setNegativeButton("Cancel", null)
                .show();
    }

    class="cd-keyword cd-access">private void openAppSettings() {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                Uri.fromParts("package", getPackageName(), null));
        startActivity(intent);
    }

    class="cd-keyword cd-access">private void openCamera() {
        // TODO: Start your camera flow
    }
}

Special cases and changes in Android 12–14

Android 13+ granular media permissions

  • Use READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIO instead of READ_EXTERNAL_STORAGE on Android 13+.
  • Prefer Photo Picker for selecting user media; it needs no storage permission.

Android 14 selected-photos access

From Android 14, users can grant partial access (“Selected photos and videos”). Your app should:

  • Detect selected-photos access by checking READ_MEDIA_VISUAL_USER_SELECTED.
  • Gracefully handle limited access and, if needed, offer a re-selection flow or direct users to App Settings to change access.
Kotlin
val hasAllImages = ContextCompat.checkSelfPermission(
context, Manifest.permission.READ_MEDIA_IMAGES
) == PackageManager.PERMISSION_GRANTED

val hasSelectedPhotosAccess = if (Build.VERSION.SDK_INT >= 34) {
ContextCompat.checkSelfPermission(
context, Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
) == PackageManager.PERMISSION_GRANTED
} else false

// If hasSelectedPhotosAccess is true but all-images isn't granted, you only see user-selected items.

Notifications (Android 13+)

Posting notifications now requires the POST_NOTIFICATIONS runtime permission on Android 13+. Ask only when you’re about to show the first meaningful notification, not on first launch. Use the same Activity Result pattern:

Kotlin
Code
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    val launcher = rememberLauncherForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { granted ->
        if (granted) showFirstNotification()
    }
    // Trigger this near an action that will post a notification
    launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
} else {
    showFirstNotification()
}

Nearby devices: Wi‑Fi and Bluetooth

  • Bluetooth (Android 12+): Request BLUETOOTH_SCAN, BLUETOOTH_CONNECT, and/or BLUETOOTH_ADVERTISE as needed. Declare location only if your use can derive location or for backward compatibility.
  • Wi‑Fi (Android 13+): Use NEARBY_WIFI_DEVICES for managing Wi‑Fi connections without location in many cases.

Background location

Background location has a staged request: first ask for foreground (FINE or COARSE). Only then can you direct the user to Settings to allow background access. Don’t pop a background location prompt at first launch.

One-time permissions and auto‑reset

  • Users can grant one-time access in dialogs for some permissions (e.g., location, mic, camera).
  • Android auto-resets permissions for unused apps. Be ready to re-check and request again when the user returns after a long time.

Handling denied results and rationale: recommended flow

  1. Check if permission is already granted.
  2. If not, evaluate shouldShowRequestPermissionRationale.
  3. Show rationale if true (the user denied once) explaining the value and privacy considerations.
  4. Request using Activity Result APIs.
  5. On denial:
    • If shouldShow... is true: keep the app usable; maybe offer another rationale later.
    • If false: treat as “Don’t ask again.” Show a small UI to open App Settings.
  6. Never hard-block the entire app if the permission isn’t critical. Offer limited functionality.

Visual flow: requesting runtime permissions the right way

User taps feature (e.g., “Take Photo”)
Check permission with checkSelfPermission
Granted → Proceed with action
Not granted → Check shouldShowRequestPermissionRationale

If true → show brief rationale, then request
If false → request directly (first time)

Result: Granted → Continue
Result: Denied

If rationale true → keep limited mode; allow retry later
If rationale false → treat as “Don’t ask again” and show Settings shortcut

Difference between manifest permissions and runtime permissions

Declaring permissions in the manifest is mandatory for any protected permission. However, dangerous permissions require a second step at runtime: checking and requesting through the system dialog. Normal permissions are granted at install time and don’t need a runtime request.

Full Kotlin snippet: camera + media + notifications with adaptive checks

Kotlin
Code
object Perms {
    const val CAMERA = Manifest.permission.CAMERA
    val MEDIA_13_PLUS = arrayOf(
        Manifest.permission.READ_MEDIA_IMAGES,
        Manifest.permission.READ_MEDIA_VIDEO
    )
    val PRE_13_STORAGE = arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE)
    const val POST_NOTIFS = Manifest.permission.POST_NOTIFICATIONS
}

class="cd-annotation">@Composable
fun PermissionsDemo(
    onCameraReady: () -> Unit,
    onMediaReady: () -> Unit,
    onNotificationsReady: () -> Unit
) {
    Column {
        CameraPermissionButton(onPermissionGranted = onCameraReady)
        Spacer(Modifier.height(16.dp))

        MediaAndNotificationPermissions(onReady = {
            onMediaReady()
            onNotificationsReady()
        })
    }
}

Output / Result

After implementing the above:

  • Tapping “Take Photo” will either open the camera (if granted) or show the system’s Android permission dialog explained (if not granted). If denied with “Don’t ask again,” the app shows a friendly message with a Settings shortcut.
  • Tapping “Browse Media and Notify” on Android 13+ requests only the relevant granular permissions. If you adopt the Photo Picker, it opens the picker without any storage permission prompts.
  • On Android 13+, the first time you try to post a notification, the system asks for notification permission, and your app proceeds only on grant.

Common mistakes to avoid

  • Requesting permissions on app startup before users perform a relevant action.
  • Relying on manifest-only declarations for dangerous permissions.
  • Using READ_EXTERNAL_STORAGE on Android 13+ without migrating to granular media permissions.
  • Forcing location permission for Bluetooth/Wi‑Fi on newer Android when nearby-device permissions suffice.
  • Looping permission prompts after denial; handle “Don’t ask again” via Settings.

FAQ: People also ask

What are runtime permissions in Android?

They are higher-risk (“dangerous”) permissions that users grant while the app is running, via a system dialog. Examples include camera, microphone, contacts, location, notifications (Android 13+), and granular media permissions (Android 13+).

When should I request a permission in an Android app?

Right before you need it, tied to a user action. For example, ask for camera permission when the user taps “Take Photo.” Provide a rationale if they previously denied.

How do I check if a permission is already granted in Android?

Use ContextCompat.checkSelfPermission(context, PERMISSION) == PackageManager.PERMISSION_GRANTED before performing the action. If granted, proceed without showing a dialog.

How do I handle the permission denied case in Android?

If denied and shouldShowRequestPermissionRationale returns true, explain why you need it and let the user try again. If it returns false, treat it as “Don’t ask again” and guide the user to App Settings.

What’s the difference between normal and dangerous permissions in Android?

Normal permissions are automatically granted at install time; dangerous permissions require explicit runtime consent via a system dialog.

Summary

Now you know how to request runtime permissions in Android the right way using modern APIs. Use Activity Result contracts for clean callbacks, show rationale when appropriate, and never block the app on non-critical permissions. Embrace newer platform behaviors—granular media permissions, selected-photos access, and the notifications permission—and where possible, use the system Photo Picker to avoid broad storage permissions. Whether you’re coding in Kotlin or following a request permission Android Java approach, a thoughtful permission UX will make your app more trustworthy and user-friendly.

Sources / Further reading

  • Permissions on Android (overview): https://developer.android.com/guide/topics/permissions/overview
  • Request runtime permissions (Activity Result APIs): https://developer.android.com/training/permissions/requesting?hl=en
  • ActivityResultContracts.RequestPermission: https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContracts.RequestPermission
  • Jetpack Compose libraries: https://developer.android.com/develop/ui/compose/libraries?hl=en
  • Notification permission (POST_NOTIFICATIONS): https://developer.android.google.cn/develop/ui/compose/notifications/notification-permission?hl=en
  • Android 13 behavior changes (granular media): https://developer.android.com/about/versions/13/behavior-changes-13
  • Android 14 partial access to photos/videos: https://developer.android.com/about/versions/14/changes/partial-photo-video-access
  • Manifest.permission reference: https://developer.android.google.cn/reference/android/Manifest.permission
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted