How it works
The explanation, with examples you can run.
The request flow
@Composablefun ScanButton(onScan: () -> Unit) { val context = LocalContext.current val launcher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> if (granted) onScan() else showWhyWeNeedTheCamera() } Button(onClick = { if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { onScan() } else { launcher.launch(Manifest.permission.CAMERA) } }) { Text("Scan") }}Good practice, in short: ask when the user starts the feature that needs it (not on first launch), check first, show an explanation when shouldShowRequestPermissionRationale() returns true, and keep the rest of the app working if the answer is no.
Things that changed across versions:
- Android 11 (API 30): denying a permission twice counts as "don't ask again". Location, microphone and camera offer "Only this time". Unused apps have their runtime permissions reset after a few months.
- Android 12 (API 31): the user can grant approximate instead of precise location; request both and handle either.
- Android 13 (API 33):
POST_NOTIFICATIONSis a runtime permission, and broad storage reads split intoREAD_MEDIA_IMAGES,READ_MEDIA_VIDEOandREAD_MEDIA_AUDIO. - Android 14 (API 34): for photos and videos, users can grant access to selected items only (
READ_MEDIA_VISUAL_USER_SELECTED); your app must cope with seeing part of the library.
Background location is a separate, second request, and Play reviews apps that use it.
Scoped storage
From Android 10, and enforced for apps targeting Android 11+, apps see their own app-specific directories without any permission, and reach shared storage only through MediaStore (their own media freely, others' media with a media permission) or the system pickers. MANAGE_EXTERNAL_STORAGE exists for file managers and backup apps and is restricted on Play.
val pick = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> uri?.let(onPicked) // no storage permission required}pick.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))Notifications on Android 13+
For apps targeting 13 or higher, you decide when the notification prompt appears, so tie it to a moment that explains the value ("Tell me when my order ships"). If the app targets 12L or lower, the system asks on its own when the app creates its first notification channel. Check NotificationManagerCompat.areNotificationsEnabled() before relying on pushes being seen.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.