Master Android Permissions in Jetpack Compose | Create a Custom Permission Manager Class

Hello guys, you always wondered why an app asks so much permission from you when installed first time. Android operating system has a policy department which says that when you access any serious feature of android device through your app then you must take permission first from the user like if any app wants to access to your device camera then that can’t use camera with your concerns means an must asks permission from the user then only access any features which are of serious nature or say can compromise user’s privacy.

In this article we will learn how to ask permission in your Android App using Jetpack Compose. We will make custom Permission Manager class which will handle our permission behind the scenes and provide us the result with all the granted and denied permission by users. For your kind information, android only allow two times to ask a permission. If user denies the any particular permission two times than an can’t ask it multiple times after that. So the only way to allow permission after that is to send user to app settings and grant that permission explicitly.

Lets first create a Permission Manager class which will take a activity context in it. In order to show permission dialog we need an activity context inside app. So the type of the activity is ComponentActivity as we want to make this Manager class universilly accessible. You can call this class from any type of Activity.

class PermissionManager(
    private val activity: ComponentActivity
) {
    
}

Now we need to declare some variables for our class. Our first variable will be a function-property or called “High-order function property” in Kotlin. It is called so because it is declared as variable so named ‘property’ and also take input and provide some output so named ‘function’. In our case it takes one input as Map of String and Boolean and return Unit means nothing useful. Then our next variable is a list of String which will contains all the denied permission by users in it.

private var permissionResults: (Map<String, Boolean>) -> Unit = {}
private val permanentlyDeniedPermissions = mutableListOf<String>()

Permissions in android has a String data type means ‘a permission is a String’.

Now we need to create a launcher for asking permissions. There are two types of launcher we can launch for asking permission as first one is asking single permission and second is to asks multiples permissions at once. When asking multiple permission you need to provide a list of permission you want to grant in this launcher. So we can use a trick and use the Multiple permission type to ask the single permission also, just by providing one permission in the list.

This launcher will provide a result to type list of Map of String and Boolean which simply give us list of Map in which String tells us the specific permission and the Boolean tells us the state of that permission either true or false(means denied). So we can use this result and can find that which permission are granted and which not.

To find the permanently denied permission from the list we need to understand a little logic that value of any key in the Map is false means that permission is denied. When a user deny a permission first time then there is a function in android to check that can we ask that permission again using- shouldShowRequestPermissionRationale(Permission). If this function returns true means that you can ask that permission again and if it returns false means you have lost all your chance to convince the user to provide you the required permission. So the second condition will be this function to find the permanently denied permission. After that we will add these permission into our list and at the end save the result in our variable.

private val permissionLauncher =
    activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { results ->
        val permanentlyDenied =
            results.filter { !it.value && !activity.shouldShowRequestPermissionRationale(it.key) }
        permanentlyDeniedPermissions.clear()
        permanentlyDeniedPermissions.addAll(permanentlyDenied.keys)
        permissionResults(results)
    }

after this create a function in our class class which will request permission by launching our launcher upon list of permission. This function will take two inputs as the list of permissions and second one is lamda function which will take the result and returns nothing.

fun requestPermissions(
    permissions: List<String>,
    onResults: (Map<String, Boolean>) -> Unit
) {
    permissionResults = onResults
    permissionLauncher.launch(permissions.toTypedArray())
}

Also create another function which will return us the list of denied permissions.

fun getPermanentlyDeniedPermissions(): List<String> = permanentlyDeniedPermissions

If the user denied the permission two times then we only left with one option that is to show a dialog which tells user that now you can only grant this permission inside this app’s settings page. So lets create a composable function to show the dialog. In confirm button of dialog we will launch an Intent which will take user to App’s Settings page.

@Composable
fun GoToSettingsDialog(
    context: Context,
    permission: String,
    onDismiss: () -> Unit
) {
    AlertDialog(
        onDismissRequest = onDismiss,
        title = {
            Text(text = "Permission Required")
        },
        text = {
            Text(text = "The following permission(s) are permanently denied: $permission. Please enable them in the app settings to use this feature.")
        },
        confirmButton = {
            TextButton(onClick = {
                onDismiss()
                context.startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
                    data = android.net.Uri.fromParts("package", context.packageName, null)
                })
            }) {
                Text("Open Settings")
            }
        },
        dismissButton = {
            TextButton(onClick = onDismiss) {
                Text("Cancel")
            }
        }
    )
}

Now declare our required permission in the App Manifest file as follows-

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />

After this come to our activity where we need to implement permission request. So first of all create instance of our Permission Manager class before setting content.

val permissionManager = PermissionManager(this@MainActivity)

Now create some variables which are useful during recompositions. First variable is same which is a Map of String and Boolean called permissionResult which we will get from our manager class. Next variable is of type String which will hold condition for showing settings dialog if not null.

var permissionResults by remember {
    mutableStateOf<Map<String, Boolean>>(emptyMap())
}
var showSettingsDialogForPermission by remember { mutableStateOf<String?>(null) }

Now create a list of permission which are going to be asked from user.

val permissions = listOf(
    android.Manifest.permission.POST_NOTIFICATIONS,
    android.Manifest.permission.RECORD_AUDIO,
    android.Manifest.permission.READ_CONTACTS
)

Now come to UI part where we will show the text for permission results and a button to launching the permission flow. For this we will be using Column composable to stack items one by one in vertical axis.

We will show Granted and Denied permissions separately , so lets find them and store in two variables. Then create an AnnotatedString with SpanStyle where color will Red for denied permissions and Green for granted permissions.

val grantedPermissions =
    permissionResults.filterValues { it }.keys.joinToString(", ")
val deniedPermissions =
    permissionResults.filterValues { !it }.keys.joinToString(", ")

val message = buildAnnotatedString {
    if (grantedPermissions.isNotEmpty()) withStyle(style = SpanStyle(color = Color.Green)) {
        append("Granted: \n $grantedPermissions. \n \n")
    }
    if (deniedPermissions.isNotEmpty()) withStyle(style = SpanStyle(color = Color.Red)) {
        append("Denied: \n $deniedPermissions.")
    }
}

Text(
    message,
    Modifier.fillMaxWidth(),
    fontSize = 20.sp,
    textAlign = TextAlign.Center
)

After we will write our main logic in the onClick of button. Call the requestPermission function which will provide us the result. Then inside the function update our local variable with lamda result and also get the list of denied permission. If there are some items in the permanently denied permission list then add that into our local variable for showing dialog.

Button(onClick = {
    permissionManager.requestPermissions(permissions) { results ->
        permissionResults = results
        val permanentlyDenied = permissionManager.getPermanentlyDeniedPermissions()
        if (permanentlyDenied.isNotEmpty()) {
            showSettingsDialogForPermission = permanentlyDenied.joinToString(", ")
        }
    }
}) {
    Text("Request Permissions")
}

Finally based on the variable show the settings dialog.

// Show dialog for permanently denied permissions
showSettingsDialogForPermission?.let { permission ->
    GoToSettingsDialog(
        context = this@MainActivity,
        permission = permission,
        onDismiss = { showSettingsDialogForPermission = null }
    )
}

Source Code: here

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top