
In this article, we will learn how to request permissions in Jetpack Compose. We will also look at how to handle multiple permissions.
Prerequisites:
For this article, add the following in the manifest file:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
There are 2 APIs to handle permissions in Jetpack Compose:
1. Requesting Permissions Using rememberLauncherForActivityResult API:
The API looks like this:
@Composable
public fun <I, O> rememberLauncherForActivityResult(
contract: ActivityResultContract<I, O>,
onResult: (O) -> Unit
): ManagedActivityResultLauncher<I, O>
It takes two parameters:
contract – It is the type of contract. We use RequestPermission() for single permission. RequestMultiplePermissions() is used for multiple permissions. You can find all the contracts here.
onResult – It is called when the user taps on Allow or Deny button. It contains the boolean value that shows if permission is granted or not.
It returns the launcher object that provides launch() method. We call the method to display the permission dialog.
Here is the complete code:
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.core.content.ContextCompat
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
YourProjectNameTheme(darkTheme = false) {
var permissionGranted by remember {
mutableStateOf(isPermissionGranted())
}
val permissionLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission()) { permissionGranted_ ->
// this is called when the user selects allow or deny
Toast.makeText(
this@MainActivity,
"permissionGranted_ $permissionGranted_",
Toast.LENGTH_SHORT
).show()
permissionGranted = permissionGranted_
}
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
enabled = !permissionGranted, // if the permission is NOT granted, enable the button
onClick = {
if (!permissionGranted) {
// ask for permission
permissionLauncher.launch(Manifest.permission.CAMERA)
}
}) {
Text(text = if (permissionGranted) "Permission Granted" else "Enable Permission")
}
if (permissionGranted) {
// update your UI
Toast.makeText(
this@MainActivity,
"Permission granted",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
}
// check initially if the permission is granted
private fun isPermissionGranted(): Boolean {
return ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
}
}
Output:

Let’s understand it.
First, we have created a state variable called permissionGranted. It remembers the permission state. For the initial value, we have called the isPermissionGranted() method. It returns true if the user granted the camera permission, otherwise false.
Next, we called the rememberLauncherForActivityResult() and passed RequestPermission() to the contract. The onResult block provides a boolean variable (permissionGranted_) that shows if the user selected Allow or not. We assigned it to our permissionGranted.
We have created a button and called the launch() method in the onClick block. When the user taps on the button, the dialog will be shown. When they allow it, the onResult block is called and our state variable (permissionGranted) will be changed to true. As a result, the button will be disabled.
Handling Multiple Permissions:
Using the launcher API, we can even request multiple permissions at once.
Complete code:
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.core.content.ContextCompat
class MainActivity : ComponentActivity() {
private val permissionsRequired = arrayOf(
Manifest.permission.CALL_PHONE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO
)
// this is to hold the permissions that are not granted
private val askPermissions = arrayListOf<String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
YourProjectNameTheme(darkTheme = false) {
var allPermissionGranted by remember {
mutableStateOf(false)
}
val permissionsLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestMultiplePermissions()) { permissionsMap ->
Toast.makeText(
this@MainActivity,
"permissionsMap $permissionsMap",
Toast.LENGTH_SHORT
).show()
// if the map does NOT contain false,
// all the permissions are granted
allPermissionGranted = !permissionsMap.containsValue(false)
}
// add the permissions that are NOT granted
for (permission in permissionsRequired) {
if (ContextCompat.checkSelfPermission(
this@MainActivity,
permission
) != PackageManager.PERMISSION_GRANTED
) {
askPermissions.add(permission)
}
}
// if the list if empty, all permissions are granted
allPermissionGranted = askPermissions.isEmpty()
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
enabled = !allPermissionGranted, // if the permissions are NOT granted, enable the button
onClick = {
if (!allPermissionGranted) {
// ask for permission
permissionsLauncher.launch(askPermissions.toTypedArray())
}
}) {
Text(text = if (allPermissionGranted) "All Permissions Granted" else "Enable Permissions")
}
if (allPermissionGranted) {
// update your UI
Toast.makeText(
this@MainActivity,
"All Permissions Granted",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
}
}
Output:

First, we have created two arrays:
permissionsRequired – It contains the required permissions.
askPermissions – We will add the permissions that are not granted to it later.
In the onCreate(), We declared a state variable called allPermissionGranted. Initially, it is false. When the user enables all the permissions, it becomes true.
Next, we called the rememberLauncherForActivityResult() method by passing RequestMultiplePermissions() to the contract. The onResult block provides a map object (permissionsMap). It holds the string and boolean pairs. The string represents the permission and the boolean value indicates if the permission is granted. We are checking if the map contains false. If it doesn’t, all the values are true which means all the permissions are granted.
Next, in the for loop, we are checking if all the required permissions are granted. If a permission is not granted, we are adding it to the askPermissions array.
Next, we created a button and called the launch() method in its onClick block. When the user taps on the button, Android shows the permission dialogs. When they are finished, the onResult block will be called and the allPermissionGranted will be updated.
2. Requesting Permissions Using Accompanist API:
First, add the following dependency in the app-level build.gradle file.
implementation "com.google.accompanist:accompanist-permissions:0.25.1"
Next, Open MainActivity. We need the PermissionState object. We can get it from the rememberPermissionState() method.
@ExperimentalPermissionsApi
@Composable
fun rememberPermissionState(
permission: String,
onPermissionResult: (Boolean) -> Unit = {}
): PermissionState
permission – The permission to be requested.
onPermissionResult – It is called when the user denies or accepts permission.
Let’s request the camera permission. Create a state variable inside the onCreate() method.
val cameraPermission =
rememberPermissionState(permission = "android.permission.CAMERA")
Next, we can display the dialog by calling the launchPermissionRequest() method.
cameraPermission.launchPermissionRequest()
To check if permission is granted or not, use the status object. There are two values – Granted and Denied.
Here is the complete code:
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionStatus
import com.google.accompanist.permissions.rememberPermissionState
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalPermissionsApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BlogPostsTheme(darkTheme = false) {
// it remembers the permission state
val cameraPermission =
rememberPermissionState(permission = "android.permission.CAMERA")
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
enabled = (cameraPermission.status != PermissionStatus.Granted), // if the permission is NOT granted, enable the button
onClick = {
cameraPermission.launchPermissionRequest()
}) {
Text(if (cameraPermission.status == PermissionStatus.Granted) "Permission Granted" else "Enable Permission")
}
}
// you can also check cameraPermission.status.isGranted
if (cameraPermission.status == PermissionStatus.Granted) {
// update the UI
Toast.makeText(
this@MainActivity,
"Permission Granted",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
}
Output:

Handling Multiple Permissions:
It’s easy to request multiple permissions using the Accompanist library. First, declare a list of permissions.
private val permissionsRequired = listOf(
Manifest.permission.CALL_PHONE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO
)
Next, call the rememberMultiplePermissionsState() method and pass the list. It returns the MultiplePermissionsState object.
val permissionsState =
rememberMultiplePermissionsState(permissions = permissionsRequired)
To display the dialogs, call launchMultiplePermissionRequest().
permissionsState.launchMultiplePermissionRequest()
To check if all the permissions are granted, use the allPermissionsGranted object.
Full code:
import android.Manifest
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState
class MainActivity : ComponentActivity() {
private val permissionsRequired = listOf(
Manifest.permission.CALL_PHONE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO
)
@OptIn(ExperimentalPermissionsApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
YourProjectNameTheme(darkTheme = false) {
val permissionsState =
rememberMultiplePermissionsState(permissions = permissionsRequired)
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
enabled = !permissionsState.allPermissionsGranted, // if the permissions are NOT granted
onClick = {
permissionsState.launchMultiplePermissionRequest()
}) {
Text(if (permissionsState.allPermissionsGranted) "All Permissions Granted" else "Enable Permissions")
}
}
if (permissionsState.allPermissionsGranted) {
// update the UI
Toast.makeText(
this@MainActivity,
"All Permission Granted",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
}
Output:

How to Detect “Don’t Ask Again” While Requesting for Permissions?
You probably have observed the “Don’t Ask Again” option on the permission dialog.

It appears if the user initially denies the permission, and you request it again. If the user selects it, the subsequent calls to the launch() methods result in no action.
Android provides shouldShowRequestPermissionRationale() method. But, there are problems with it. Look at the comments on this answer.
If you want to implement it, follow these guides:
If you don’t want it, add a text button that opens the settings page of your app.
Example:

Code:
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.sp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
YourProjectNameTheme(darkTheme = false) {
// add your permissions code
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
onClick = {
// add your permissions code here
}) {
Text("Enable Permissions")
}
val annotatedText = buildAnnotatedString {
withStyle(
style = SpanStyle(
color = Color.DarkGray,
fontSize = 16.sp
)
) {
append("You can also enable permissions ")
}
pushStringAnnotation(
tag = "settings",
annotation = "settings"
)
withStyle(
style = SpanStyle(
color = Color.Blue,
fontSize = 16.sp
)
) {
append("in the settings")
}
pop()
}
ClickableText(
text = annotatedText,
onClick = { offset ->
annotatedText.getStringAnnotations(
tag = "settings",// tag which you used in the buildAnnotatedString
start = offset,
end = offset
)[0].let {
// onClick block
// open settings
val intentSettings =
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intentSettings.data =
Uri.fromParts("package", packageName, null)
startActivity(intentSettings)
}
}
)
}
}
}
}
}
}
Bonus:
Sometimes when you request multiple permissions, you don’t want to request all of them at once. You may want to check the result of the individual consent. In such cases, you can use the following logic.
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.core.content.ContextCompat
class MainActivity : ComponentActivity() {
private val permissionsRequired = arrayOf(
Manifest.permission.CALL_PHONE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO
)
private lateinit var allPermissionsGranted: MutableState<Boolean>
private lateinit var nextPermission: MutableState<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BlogPostsTheme(darkTheme = false) {
allPermissionsGranted = remember {
mutableStateOf(false)
}
nextPermission = remember {
mutableStateOf(permissionsRequired[0])
}
val permissionRequestLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission()) { permissionGranted ->
Toast.makeText(
this.applicationContext,
"permissionGranted $permissionGranted",
Toast.LENGTH_SHORT
).show()
checkPermissions()
}
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
enabled = !allPermissionsGranted.value, // if the permissions are NOT granted, enable the button
onClick = {
permissionRequestLauncher.launch(nextPermission.value)
}) {
Text(
text = if (allPermissionsGranted.value) "All Permissions are Granted" else
nextPermission.value.substringAfterLast(".")
)
}
if (allPermissionsGranted.value) {
// update UI
Toast.makeText(
this@MainActivity,
"All Permissions are Granted",
Toast.LENGTH_SHORT
).show()
}
}
}
// this is called when the app is launched first time
LaunchedEffect(Unit) {
checkPermissions()
}
}
}
}
private fun checkPermissions() {
// check if all the permissions are granted
for (permission in permissionsRequired) {
if (ContextCompat.checkSelfPermission(
this@MainActivity,
permission
) != PackageManager.PERMISSION_GRANTED
) {
// a permission is not granted
allPermissionsGranted.value = false
nextPermission.value = permission
return
}
}
// all permissions are granted
allPermissionsGranted.value = true
}
}
Output:

This is all about requesting permissions in Android Jetpack Compose. I hope you have learned something new. If you have any doubts, leave a comment below.
Related:
- Check Internet Connection in Android Programatically
- Animate Content Changes in Jetpack Compose
- 5 Awesome Loading Dialogs Made with Jetpack Compose
- AppLovin MAX Ads Integration
References: