Android Runtime permissions with AndroidX RequestPermission contract — simple sample
Android Runtime permissions with AndroidX RequestPermission contract — simple sample
1.Before you begin:
In this blog we will learn how to implement Android runtime permissions and how to declare them if app is installed on devices that runs Android 6.0 (API level 23) or higher .
use the RequestPermission
contract, included in an AndroidX library, where we allow the system to manage the permission request code rather than handling with callbacks.
Introduction:
App permissions help support user privacy by protecting access to the following:
- Restricted data, such as system state and a user’s contact information.
- Restricted actions, such as connecting to a paired device and recording audio.
Types of permissions:
- Install-time permissions
- Normal permissions
- Signature permissions
- Runtime permissions
In this blog we will be discussing on runtime permissions
What are Runtime permissions?
Runtime permissions, also known as dangerous permissions, give your app additional access to restricted data, and they allow your app to 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. When your app requests a runtime permission, the system presents a runtime permission prompt
Many runtime permissions access private user data, a special type of restricted data that includes potentially sensitive information. Examples of private user data include location and contact information.
Prerequisites:
- Experience with Kotlin syntax, including lambdas.
- A basic understanding of Navigation component, permissions, Android Fragments, Activities
What you’ll do ?
A sample application with below two screens that demonstrates single/multiple runtime permissions:
- Launch Camera screen : This Fragment demonstrates providing camera permission at runtime and allows to launch camera when granted permissions. This screen allows to navigate to next screen.
- Contacts Screen: This Fragment demonstrates accessing multiple permissions at runtime and allows to read/write contacts from device when granted permissions.
2.Getting Setup :
The code is available in the above Github repository.
gitclone: https://github.com/chandragithub2014/PermissionsExample.git
3. Run the App:
Screen shots for different scenarios:
Explanation:
PermissionHelper:
This class contains various methods to handle Runtime permissions. This class is single point of contact for all the runtime permission handling. Activity/Fragment can use this helper class to handle all the runtime permissions.
Workflow for requesting permissions:
- Determine whether app was already granted permission:
ContextCompat.checkSelfPermission(): Checks if the user has already granted your app a particular permission.
This method returns either PERMISSION_GRANTED
or PERMISSION_DENIED
, depending on whether your app has the permission.
This is the first method that needs to be called as part of checking whether particular permission is granted or not.
Code sample for the above step:
context?.requireContext()?.let {
ContextCompat.checkSelfPermission(
it,
manifestPermission
)
} == PackageManager.PERMISSION_GRANTED -> {
println("Permission Granted....")
}
Where context is fragment’s context and manifestPermission is the permission to be checked whether it is granted or not and it should be declared in manifest file.
In this example “manifestPermission” is Manifest.permission.CAMERA.
2.Explain why app needs permission :
In the above step if the “ContextCompat.checkSelfPermission()” method returns PERMISSION_DENIED
, then call shouldShowRequestPermissionRationale().If this method returns true
, show an educational UI to the user. In this UI, describe why the feature, which the user wants to enable, needs a particular permission.
In this app , I will be displaying an alert dialog that shows purpose of the permission. If users clicks “OK” button in alert dialog, I will be shown system permission dialog to either grant/deny permission.
ActivityCompat.shouldShowRequestPermissionRationale(
context?.requireContext() as Activity,
manifestPermission
) -> {
println("Show Request Permission Rationale")
//Display Alert Dialog.
}
3.Request permissions:
After the user views an educational UI (Alert Dialog in our case) , if the users clicks “OK” button in Alert Dialog to again request the permission then Users see a system permission dialog, where they can choose whether to grant a particular permission to your app.
Important Note:
Traditionally, we can manage a request code ourselves as part of the permission request and include this request code in your permission callback logic.
Another option is to use the RequestPermission contract, included in an AndroidX library, where you allow the system to manage the permission request code for you. Because using the RequestPermission contract simplifies your logic, it’s recommended that you use it when possible.
RequestPermission contract :
RequestPermission contract contains 2 classes:
RequestPermission:To request a single permission
2. RequestMultiplePermissions:To request multiple permissions at the same time.
In your activity or fragment’s initialization logic, pass in an implementation of
ActivityResultCallback
into a call toregisterForActivityResult()
. TheActivityResultCallback
defines how your app handles the user's response to the permission request.Keep a reference to the return value of
registerForActivityResult()
, which is of typeActivityResultLauncher
.To display the system permissions dialog when necessary, call the
launch()
method on the instance ofActivityResultLauncher
that you saved in the previous step.
Code sample for the above explained steps:
RequestPermission:
private val requestPermissionLauncher =
context.registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
Log.i("Permission: ", "Granted")
} else {
Log.i("Permission: ", "Denied")
}
}
RequestMultiplePermissions
private val requestMultiplePermissionsLauncher = context.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
permissions.entries.forEach {
Log.i("DEBUG", "${it.key} = ${it.value}")
if(it.value){
println("Successful......")
}
}
}
Using launch():
fun launchPermissionDialogForMultiplePermissions(manifestPermissions: Array<String>){
requestMultiplePermissionsLauncher.launch(manifestPermissions)
} fun launchPermissionDialog(manifestPermission: String){
requestPermissionLauncher.launch(
manifestPermission
)
}
Where manifestPermissions: Array of Multiple permissions .(In our case it is READ and WRITE contacts)
where manifestPermission: Is the single permission (In our case it is CAMERA)
PermissionHelper: Putting all discussed above in a single class
class PermissionHelper(context: Fragment,permissionListener: PermissionListener) {
private var context: Fragment? = null
private var permissionListener : PermissionListener? = null
init {
this.context = context
this.permissionListener = permissionListener
}
private val requestPermissionLauncher =
context.registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
Log.i("Permission: ", "Granted")
permissionListener?.isPermissionGranted(true)
} else {
Log.i("Permission: ", "Denied")
}
}
private val requestMultiplePermissionsLauncher = context.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
permissions.entries.forEach {
Log.i("DEBUG", "${it.key} = ${it.value}")
if(it.value){
println("Successful......")
permissionListener?.isPermissionGranted(true)
}
}
}
fun checkForPermissions(manifestPermission: String) {
when {
context?.requireContext()?.let {
ContextCompat.checkSelfPermission(
it,
manifestPermission
)
} == PackageManager.PERMISSION_GRANTED -> {
println("Permission Granted....")
permissionListener?.isPermissionGranted(true)
}
ActivityCompat.shouldShowRequestPermissionRationale(
context?.requireContext() as Activity,
manifestPermission
) -> {
println("Show Request Permission Rationale")
permissionListener?.isPermissionGranted(false)
permissionListener?.shouldShowRationaleInfo()
}
else -> {
launchPermissionDialog(manifestPermission)
println("Final Else....")
}
}
}
private var isDenied : Boolean = false
fun checkForMultiplePermissions(manifestPermissions: Array<String>) {
for (permission in manifestPermissions) {
context?.requireContext()?.let {
if (ContextCompat.checkSelfPermission(
it,
permission
) == PackageManager.PERMISSION_GRANTED
) {
println("Permission Granted....")
permissionListener?.isPermissionGranted(true)
} else if (ActivityCompat.shouldShowRequestPermissionRationale(
context?.requireContext() as Activity,
permission
)
) {
isDenied = true
// requestMultiplePermissionsLauncher.launch(manifestPermissions)
} else {
requestMultiplePermissionsLauncher.launch(manifestPermissions)
}
}
}
if(isDenied){
permissionListener?.isPermissionGranted(false)
permissionListener?.shouldShowRationaleInfo()
}
}
fun launchPermissionDialogForMultiplePermissions(manifestPermissions: Array<String>){
requestMultiplePermissionsLauncher.launch(manifestPermissions)
}
fun launchPermissionDialog(manifestPermission: String){
requestPermissionLauncher.launch(
manifestPermission
)
}
}
In the above we have two functions to handle single/multiple permissions as part of explanation purpose:
fun checkForMultiplePermissions(manifestPermissions: Array<String>)
//Handles Multiple permissions logicfun checkForPermissions() // To handle single permission logic
PermissionListener: This is the interface implemented in Fragment/Activity to display alert dialog(Display info about importance of permission) or open camera or allow read/write contacts when permission is granted.
interface PermissionListener {
fun shouldShowRationaleInfo()
fun isPermissionGranted(isGranted : Boolean)
}
CameraPermissionsFragment:
This the fragment that allows to access camera if permission is Granted. Also contains a button to navigate to next screen.
This is the place where we initialize PermissionHelper class and handle callbacks to display AlertDialog and launch camera.
Initialize Permission Helper:
//Initialize PermissionHelper
permissionHelper = PermissionHelper(this,this)//Make call to singlePermission logicpermissionHelper.checkForPermissions(Manifest.permission.CAMERA)
ReadWriteContactsFragment:
This Fragment demonstrates accessing multiple permissions at runtime and allows to read/write contacts from device when granted permissions.
permissionHelper = PermissionHelper(this, this)permissionHelper.checkForMultiplePermissions(
arrayOf(
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS
)
)
In the above code sample we initialize PermissionHelper and make a call to checkForMultiplePermissions() method which array of permissions declared in manifest.
Congratulations:
In this blogs, we have covered handling Runtime permissions in Android app using RequestPermission contract, included in an AndroidX library instead of handling ourselves to simplify permission handling with a simple example.
PermissionsHelper can be included in any project to handle runtime permissions and customize accordingly as per needs in callbacks.
References:
https://developer.android.com/training/permissions/requesting
https://developer.android.com/guide/topics/permissions/overview#runtime