Requesting Permission Jetpack Compose β€” The Complete Guide

πŸš€ Hey, Jetpack Compose Developer! ✨ There is a lot of things required while making an app Using Jetpack Compose and one of the Important Topic is Requesting Different type of User permissions using Jetpack Compose.

πŸ“± Runtime permissions πŸ”‘πŸ“¦

Runtime permissions, also known as dangerous permissions, give your app additional access to restricted data or let your app perform restricted actions that more substantially affect the system and other apps. Therefore, you need to request runtime permissions in your app before you can access the restricted data or perform restricted actions. Don’t assume that these permissions have been previously granted β€” check them and, if needed, request them before each access.

We are familiar with how to request permission in an XML-based Android app but How do it with Jetpack ComposeπŸ’‘?

πŸ“š Read More: https://google.github.io/accompanist/

πŸš€ Let’s start an example by requesting permission using Jetpack Compose And Google Accompanist.

Step 1: Google Accompanist Dependency

    implementation("com.google.accompanist:accompanist-permissions:0.31.0-alpha")

Step 2: Add Permissions In AndroidManifest.xml File

πŸ—’οΈ Note :We are using POST_NOTIFICATIONS permissions for our example project

We need to declare the required permission in AndroidManifest.xml file. If you do not put it will not Trigger permission or Android Studio Lint will notify you.

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

Step 3: Checking And Requesting Permission

πŸ—’οΈ Explanations

  • Define a State Variable πŸ“Œ Which will hold the Permission Reference In the Composable Function.
  • Check For Permission Status Whether It is Granted Or Not.
  • Also, Check Whether the user Denied The Permission And Should Show an Explanation Dialog.πŸ“
  • Permission is granted to Do Further Work.
  1. Define a state variable. we are using rememberPermissionState because we require only one permission at a time.
  2. But if you need multiple permissions at a time use rememberMultiplePermissionsState and pass the List<String> to it.

2. Check the permission status on the Button click event whether is granted or not or user denied the permission before.

if (!notificationPermission.status.isGranted) {
    if (notificationPermission.status.shouldShowRationale) {
        // Show a rationale if needed (optional)
        showRationalDialog.value = true
    } else {
        // Request the permission
        notificationPermission.launchPermissionRequest()

    }
} else {
    Toast.makeText(context, "Permission Given Already", Toast.LENGTH_SHORT).show()
}

3. If the user has previously ❌ denied the permission then We have to check it using shouldShowRationale statusIf it is true we have to show an Explanation Dialog to the user so they can grant permission.

We will show an explanation Dialog to the user When the user is ❌denied permission Android System will not ask again from the user even if you request permission.

Note: we have to navigate the user to settings and enable permission manually. so when the user clicks OK.

We will navigate to the App Settings Menu Using the Below Code.

val showRationalDialog = remember { mutableStateOf(false) }
if (showRationalDialog.value) {
    AlertDialog(
        onDismissRequest = {
            showRationalDialog.value = false
        },
        title = {
            Text(
                text = "Permission",
                fontWeight = FontWeight.Bold,
                fontSize = 16.sp
            )
        },
        text = {
            Text(
                "The notification is important for this app. Please grant the permission.",
                fontSize = 16.sp
            )
        },
        confirmButton = {
            TextButton(
                onClick = {
                    showRationalDialog.value = false
                    val intent = Intent(
                        Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                        Uri.fromParts("package", context.packageName, null)
                    )
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                    startActivity(context, intent, null)

                }) {
                Text("OK", style = TextStyle(color = Color.Black))
            }
        },
        dismissButton = {
            TextButton(
                onClick = {
                    showRationalDialog.value = false
                }) {
                Text("Cancel", style = TextStyle(color = Color.Black))
            }
        },
    )
}

Step 4: Permission Granted βœ…

Once the user grants the permissions. you can do your further task. Also, check it every time before your use case to avoid any code malfunctions.

Here, we have done with Single Permission Handling 😊. Now let’s take a look at Requesting Multiple Permission In Jetpack Compose.

πŸš€ βœ… The Complete Example Of Single Permission Request

import android.Manifest
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat.startActivity
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.google.accompanist.permissions.rememberPermissionState
import com.google.accompanist.permissions.shouldShowRationale


@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class)
@Composable
fun PermissionDemo() {
    val notificationPermission = rememberPermissionState(
        permission = Manifest.permission.POST_NOTIFICATIONS
    )
    val multiplePermission = rememberMultiplePermissionsState(permissions = listOf(
        Manifest.permission.RECORD_AUDIO,
        Manifest.permission.CAMERA
    ))
    val context = LocalContext.current

    val showRationalDialog = remember { mutableStateOf(false) }
    if (showRationalDialog.value) {
        AlertDialog(
            onDismissRequest = {
                showRationalDialog.value = false
            },
            title = {
                Text(
                    text = "Permission",
                    fontWeight = FontWeight.Bold,
                    fontSize = 16.sp
                )
            },
            text = {
                Text(
                    "The notification is important for this app. Please grant the permission.",
                    fontSize = 16.sp
                )
            },
            confirmButton = {
                TextButton(
                    onClick = {
                        showRationalDialog.value = false
                        val intent = Intent(
                            Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                            Uri.fromParts("package", context.packageName, null)
                        )
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                        startActivity(context, intent, null)

                    }) {
                    Text("OK", style = TextStyle(color = Color.Black))
                }
            },
            dismissButton = {
                TextButton(
                    onClick = {
                        showRationalDialog.value = false
                    }) {
                    Text("Cancel", style = TextStyle(color = Color.Black))
                }
            },
        )
    }

    Scaffold(topBar = {
        CenterAlignedTopAppBar(
            title = { Text(text = "Request Single Permission", color = Color.White) },
            colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
                containerColor = MaterialTheme.colorScheme.primary
            )
        )
    }) {
        Box(
            modifier = Modifier.fillMaxSize().padding(it),
            contentAlignment = Alignment.Center
        ) {
            Column(horizontalAlignment = Alignment.CenterHorizontally) {
                Button(onClick = {
                    if (!notificationPermission.status.isGranted) {
                        if (notificationPermission.status.shouldShowRationale) {
                            // Show a rationale if needed (optional)
                            showRationalDialog.value = true
                        } else {
                            // Request the permission
                            notificationPermission.launchPermissionRequest()

                        }
                    } else {
                        Toast.makeText(context, "Permission Given Already", Toast.LENGTH_SHORT)
                            .show()
                    }
                }) {
                    Text(text = "Ask for permission")
                }
                Text(
                    modifier = Modifier.padding(horizontal = 20.dp, vertical = 5.dp),
                    text = if (notificationPermission.status.isGranted) {
                        "Permission Granted"
                    } else if (notificationPermission.status.shouldShowRationale) {
                        // If the user has denied the permission but the rationale can be shown,
                        // then gently explain why the app requires this permission
                        "The notification is important for this app. Please grant the permission."
                    } else {
                        // If it's the first time the user lands on this feature, or the user
                        // doesn't want to be asked again for this permission, explain that the
                        // permission is required
                        "The notification permission is required for some functionality."
                    },
                    fontWeight = FontWeight.Bold,
                    fontSize = 16.sp
                )
            }
        }
    }
}

Requesting Multiple Permissions (Explanation)

  • Define a State Variable πŸ“Œ Which will hold the Multiple Permission Reference In the Composable Function.
  • Check For All Permission Status Whether It is Completely Granted Or Partially.
  • Also, Check Whether the user ❌ Denied All the Permissions or some. When User deny some permission we will check for it by comparing with Our permission and show message as per it.
  • We can check how many permission is denied by multiplePermission.revokedPermission . it will return list of Denied permissionState.
  • We are using android.permission.CAMERA and android.permission.RECORD_AUDIO for a demo purpose only.🧩
  • Permission is granted to Do Further Work βœ….
import android.Manifest
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat.startActivity
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState


@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class)
@Composable
fun MultiplePermissionDemo() {
    val multiplePermission = rememberMultiplePermissionsState(
        permissions = listOf(
            Manifest.permission.CAMERA,
            Manifest.permission.RECORD_AUDIO
        )
    )
    val context = LocalContext.current

    val showRationalDialog = remember { mutableStateOf(false) }
    if (showRationalDialog.value) {
        AlertDialog(
            onDismissRequest = {
                showRationalDialog.value = false
            },
            title = {
                Text(
                    text = "Permission",
                    fontWeight = FontWeight.Bold,
                    fontSize = 16.sp
                )
            },
            text = {
                Text(
                    if (multiplePermission.revokedPermissions.size == 2) {
                        "We need camera and audio permission to shoot video"
                    } else if (multiplePermission.revokedPermissions.first().permission == Manifest.permission.CAMERA) {
                        "We need camera permission. Please grant the permission."
                    } else {
                        "We need audio permission. Please grant the permission."
                    },
                    fontSize = 16.sp
                )
            },
            confirmButton = {
                TextButton(
                    onClick = {
                        showRationalDialog.value = false
                        val intent = Intent(
                            Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                            Uri.fromParts("package", context.packageName, null)
                        )
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                        startActivity(context, intent, null)

                    }) {
                    Text("OK", style = TextStyle(color = Color.Black))
                }
            },
            dismissButton = {
                TextButton(
                    onClick = {
                        showRationalDialog.value = false
                    }) {
                    Text("Cancel", style = TextStyle(color = Color.Black))
                }
            },
        )
    }

    Scaffold(topBar = {
        CenterAlignedTopAppBar(
            title = { Text(text = "Request Multiple Permission", color = Color.White) },
            colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
                containerColor = MaterialTheme.colorScheme.primary
            )
        )
    }) {
        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(it),
            contentAlignment = Alignment.Center
        ) {
            Column(horizontalAlignment = Alignment.CenterHorizontally) {
                Button(onClick = {
                    if (!multiplePermission.allPermissionsGranted) {
                        if (multiplePermission.shouldShowRationale) {
                            // Show a rationale if needed (optional)
                            showRationalDialog.value = true
                        } else {
                            // Request the permission
                            multiplePermission.launchMultiplePermissionRequest()

                        }
                    } else {
                        Toast.makeText(
                            context,
                            "We have camera and audio permission",
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                }) {
                    Text(text = "Ask for permission")
                }
                Text(
                    modifier = Modifier.padding(horizontal = 20.dp, vertical = 5.dp),
                    text = if (multiplePermission.allPermissionsGranted) {
                        "All Permission Granted"
                    } else if (multiplePermission.shouldShowRationale) {
                        // If the user has denied the permission but the rationale can be shown,
                        // then gently explain why the app requires this permission
                        if (multiplePermission.revokedPermissions.size == 2) {
                            "We need camera and audio permission to shoot video"
                        } else if (multiplePermission.revokedPermissions.first().permission == Manifest.permission.CAMERA) {
                            "We need camera permission. Please grant the permission."
                        } else {
                            "We need audio permission. Please grant the permission."
                        }
                    } else {
                        // If it's the first time the user lands on this feature, or the user
                        // doesn't want to be asked again for this permission, explain that the
                        // permission is required
                        "We need camera and audio permission to shoot video"
                    },
                    fontWeight = FontWeight.Bold,
                    fontSize = 16.sp
                )
            }
        }
    }
}

🚨🚫 Limitations of Google Accompanist πŸš«πŸš¨

This permissions wrapper is built on top of the available Android platform APIs. We cannot extend the platform’s capabilities. For example, it’s not possible to differentiate between the it’s the first time requesting the permission vs the user doesn’t want to be asked again use cases.

🍴Check out the complete code on my GitHub Gist. βœοΈ Hope this project really helps you.

Hope you enjoy coding Jetpack Compose πŸ˜. Don’t forget to share πŸ“¨ and clap πŸ‘.

Any Suggestions are welcome. If you need any help or have questions for Code Contact Me. You can follow me on LinkedInStackOverflow and Twitter For More Updates πŸ””

Happy Compose !! πŸš€

Referece :Β https://developer.android.com/guide/topics/permissions/overview

Leave a Comment