runtime permission in jetpack compose poster

Best ways to handle runtime permission on jetpack compose

If your app is installed on a device that runs Android 6.0 (API level 23) or higher, you must request the runtime permissions for the user by following the steps in this guide.

There are two ways to get runtime permissions in jetpack compose.

  1. Using activity result
  2. Using the accompanist permissions library

let’s have a deep look into the above methods in detail with examples.

Runtime permission using activity result

The first step is to define the permission in the manifest.xml file.

<uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera"/>

In this sample application, I am using a camera permission example to capture the image. check my other example to learn more about capturing images using the camera.

How to capture images using the camera in jetpack compose (howtodoandroid.com)

How to pick Image from gallery in jetpack compose (howtodoandroid.com)

Create an activity result launcher to request the permission we defined. Once it’s launched it will return the result whether the permission is granted or not.

val permissionLauncher = rememberLauncherForActivityResult(
            ActivityResultContracts.RequestPermission()
        ) {
            if (it) {
                Toast.makeText(context, "Permission Granted", Toast.LENGTH_SHORT).show()
                cameraLauncher.launch(uri)
            } else {
                Toast.makeText(context, "Permission Denied", Toast.LENGTH_SHORT).show()
            }

        }

Checking Permission

Before launching the request for permission, we need to check whether the permission is granted or not. If it’s already granted we can proceed with our regular flow. If permission is not provided, then we need to launch the permission request with the permission we wanted.

val permissionCheckResult = ContextCompat.checkSelfPermission(context, android.Manifest.permission.CAMERA)

                if (permissionCheckResult == PackageManager.PERMISSION_GRANTED) {
                    cameraLauncher.launch(uri)
                } else {
      permissionLauncher.launch(android.Manifest.permission.CAMERA)
                }

finally, the code for the runtime permission using the activity result will be like the below,

val context = LocalContext.current
        val file = context.createImageFile()
        val uri = FileProvider.getUriForFile(
            Objects.requireNonNull(context),
            BuildConfig.APPLICATION_ID + ".provider", file
        )

        var capturedImageUri by remember {
            mutableStateOf<Uri>(Uri.EMPTY)
        }

        val cameraLauncher =
            rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) {
                capturedImageUri = uri
            }
        val permissionLauncher = rememberLauncherForActivityResult(
            ActivityResultContracts.RequestPermission()
        ) {
            if (it) {
                Toast.makeText(context, "Permission Granted", Toast.LENGTH_SHORT).show()
                cameraLauncher.launch(uri)
            } else {
                Toast.makeText(context, "Permission Denied", Toast.LENGTH_SHORT).show()
            }

        }

        Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(12.dp)) {
            Button(onClick = {
                val permissionCheckResult = ContextCompat.checkSelfPermission(context, android.Manifest.permission.CAMERA)

                if (permissionCheckResult == PackageManager.PERMISSION_GRANTED) {
                    cameraLauncher.launch(uri)
                } else {
                    // Request a permission
                    permissionLauncher.launch(android.Manifest.permission.CAMERA)
                }

            }) {
                Text(text = "Open Camera")
            }

            if (capturedImageUri.path?.isNotEmpty() == true) {
                Image(
                    modifier = Modifier
                        .padding(16.dp, 8.dp)
                        .fillMaxWidth()
                        .size(400.dp),
                    painter = rememberImagePainter(capturedImageUri),
                    contentDescription = null
                )
            }
        }

the output of the above code,

asking runtime permission to user

Asking multiple permissions

In some cases, we need to ask the multiple permissions at a time, for example, location permissions. For that, we need to use different launcher methods and permission checks. let’s see that in detail.

As usual, we need to define the needed permissions in the manifest.xml file.

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

The need step is to create the list with the permissions we are going to request.

 val permissions = arrayOf(
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.ACCESS_FINE_LOCATION
        )

Use the ActivityResultContracts.RequestMultiplePermissions() method to request multiple permissions at a time. use this function and create the permission launcher.

val launcherMultiplePermissions = rememberLauncherForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()
        ) { permissionsMap ->
            val areGranted = permissionsMap.values.reduce { acc, next -> acc && next }
            if (areGranted) {
                Toast.makeText(context, "Permission Granted", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(context, "Permission Denied", Toast.LENGTH_SHORT).show()
            }
        }

Now, the launcher is ready for multiple permission. the next step is to check whether the permission is already granted or not. if it’s not granted then launch the permission launcher with the list of permissions.

if(permissions.all {
        ContextCompat.checkSelfPermission(
            context,
            it
        ) == PackageManager.PERMISSION_GRANTED
    }) {
        // Get the location
    } else {
        launcherMultiplePermissions.launch(permissions)
    }

the final code for requesting multiple runtime permission will be,

val permissions = arrayOf(
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.ACCESS_FINE_LOCATION
        )

        val launcherMultiplePermissions = rememberLauncherForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()
        ) { permissionsMap ->
            val areGranted = permissionsMap.values.reduce { acc, next -> acc && next }
            if (areGranted) {
                Toast.makeText(context, "Permission Granted", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(context, "Permission Denied", Toast.LENGTH_SHORT).show()
            }
        }

        Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(12.dp)) {
            Button(onClick = {

                if(permissions.all {
                    ContextCompat.checkSelfPermission(
                        context,
                        it
                    ) == PackageManager.PERMISSION_GRANTED
                }) {
                    // Get the location
                } else {
                    launcherMultiplePermissions.launch(permissions)
                }

            }) {
                Text(text = "Get Current Location")
            }

        }

Using accompanist permissions library

The second way of getting the runtime permission is very easy compared to the first one because in this method we are using the accompanist permission library. add the library in your build.gradle file.

implementation "com.google.accompanist:accompanist -permissions:0.23.1"

After adding the dependency, need to use rememberPermissionState() a method to maintain the state for the permission we are passing to it. Also, use PermissionRequired() to observe the permission state on runtime. It contains the view for the all the permission states like permission granted or permission denied.

val permissionState = rememberPermissionState(permission = Manifest.permission.CAMERA)

        PermissionRequired(
            permissionState = permissionState,
            permissionNotGrantedContent = {
                Toast.makeText(context, "Permission not granted", Toast.LENGTH_SHORT).show()
            },
            permissionNotAvailableContent = {
                Toast.makeText(context, "Permission not available", Toast.LENGTH_SHORT).show()
            }) {
            if (isShowCamera) {
                cameraLauncher.launch(uri)
                isShowCamera = false
            }
        }

Since, we are using the camera in this sample, need to create the camera launcher to capture the image from the activity result.

val cameraLauncher =
            rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) {
                isShowCamera = false
                capturedImageUri = uri
            }

Now we are done with the setup of the permission state and the camera launcher. the next step is to use the permission. with the click of, any button or action we need to call them permissionState.hasPermission to check whether the permission is granted or not. if it’s not granted we need to call the permission state launcher for the runtime permission dialog.

Button(onClick = {
        if(permissionState.hasPermission) {
            cameraLauncher.launch(uri)
        } else {
            isShowCamera = true
            permissionState.launchPermissionRequest()
        }
    }) {
        Text(text = "Open Camera")
    }

in the same way, you can ask the multiple permissions using the accompanist library. check the below code for the multiple permissions with the accompanist.

val permissionsState = rememberMultiplePermissionsState(
        permissions = listOf(
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.ACCESS_FINE_LOCATION
        )
    )

    PermissionsRequired(
    multiplePermissionsState = permissionsState,
    permissionsNotGrantedContent = {
        Toast.makeText(context, "Permission not granted", Toast.LENGTH_SHORT).show()
    },
    permissionsNotAvailableContent = {
        Toast.makeText(context, "Permission not available", Toast.LENGTH_SHORT).show()
    }) {
        //content

    }
    Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(12.dp)) {
        Button(onClick = {

            if(permissionsState.permissions.all {
                    ContextCompat.checkSelfPermission(
                        context,
                        it.permission
                    ) == PackageManager.PERMISSION_GRANTED
                }) {
                // access location

            } else {

                permissionsState.launchMultiplePermissionRequest()
            }
        }) {
            Text(text = "Location Permission")
        }

    }

demo application screenshot

Done with the explanation, and thanks for reading. you can download this sample source code on GitHub.


Comments

Leave a Reply

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


Latest Posts