How to Request Permissions in Jetpack Compose (2 Ways)

Request Permission Jetpack Compose

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. rememberLauncherForActivityResult API
  2. Accompanist Permissions API (experimental)

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:

single permission handling

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:

request multiple permissions

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:

single permission accompanist

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:

request multiple permissions accompanist

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.

don't ask again

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:

text button app settings permissions

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:

multiple permissions

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:

References:

Leave a Comment