π 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.
- Define a state variable. we are using
rememberPermissionState
because we require only one permission at a time. - But if you need multiple permissions at a time use
rememberMultiplePermissionsState
and pass theList<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
status. If 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 DeniedpermissionState.
- We are using
android.permission.CAMERA
andandroid.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 LinkedIn, StackOverflow and Twitter For More Updates π
Happy Compose !! π
Referece :Β https://developer.android.com/guide/topics/permissions/overview