Hello everyone!
Today we will be talking about permission handling in android jetpack compose using google’s accompanist library.

We will discuss various use cases and permission handling scenarios in this article. By the end of it all, you will have everything with you to manage permissions in your android app.

The entire code discussed can be found in the repository given below.

Here is a little preview of what we will be building:

A simple registration page UI

If you don’t want to use Accompanist for adding requesting permission, you can refer to my another article.
We will also discuss few use cases that you might encounter while handling permissions in the app after completing the implementation of permissions.

Dependencies

implementation "com.google.accompanist:accompanist-permissions:0.21.1-beta"

Note: The Accompanist library is new and experimental. There might be changes introduced in the future that may make the code erroneous.

1. Adding permissions to Manifest file

Android provides you with various permissions to choose from and to add to your application. You can find them all here.
Since we will be working with camera permission, add the following code to your android manifest file.

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

2. Accessing the Camera

Now let us bring our accompanist library to use and access our camera permission.
Given below, we have created a composable cameraPermissionState( ), where

@Composable
fun cameraPermissionState(): PermissionState {
    return rememberPermissionState(permission = CAMERA_PERMISSION)
}

This composable returns the state of our camera permission, whether the system has granted the camera permission or not.
rememberPermissionState( ) is a public function provided by the accompanist library which maintains our permission state throughout.

Note: rememberPermissionState( ) is still an experimental api. Make sure you add @OptIn(ExperimentalPermissionsApi::class) to our composable HomeScreen( ).

Therefore code looks like

@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun cameraPermissionState(): PermissionState {
    return rememberPermissionState(permission = CAMERA_PERMISSION)
}

Additionally

In case of multiple permissions, you can use rememberMultiplePermissionState( ) which functions the same as rememberPermissionState( ), but for multiple permissions. Do not forget to add @OptIn(ExperimentalPermissionsApi::class) annotation for the same.

val totalPermissions = rememberMultiplePermissionsState(
    permissions = listOf(
        Manifest.permission.CAMERA,
        Manifest.permission.ACCESS_FINE_LOCATION
    )
)

3. Loading the image

After adding the composable to return our camera permission. Let us now add some variables to fetch our captured image to the screen.

Here, we have created another composable ProfileScreenWithAccompanist( ), where we build our profile screen. Variables are added below.

@Composable
fun ProfileScreenWithAccompanist() {
    var resultBitmap: Bitmap? by rememberSaveable { mutableStateOf(placeHolderBitmap) }

    val launcherForImageCapture = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.TakePicturePreview()
    ) {
        resultBitmap = if (it.toString().isEmpty()) {
            placeHolderBitmap
        } else {
            it
        }
    }
}

Here,
1. resultBitmap is our variable that stores the image captured in Bitmap.
2. Another variable launcherForImageCapture launches the camera to capture the image.

Note: placeHolderBitmap is a variable that is used as a place holder before the image is clicked.

val placeHolderBitmap: Bitmap = BitmapFactory.decodeResource(
    Resources.getSystem(),
    android.R.drawable.ic_menu_camera
)

4. Adding the permissions logic

Now, let us add our permission handling logic to an Image.

Image(
    bitmap = it,
    contentDescription = "Captured image",
    contentScale = ContentScale.Crop,
    modifier = Modifier
        .size(150.dp)
        .clickable {
            if (cameraPermission.hasPermission) {
                launcherForImageCapture.launch()
            } else if (!cameraPermission.hasPermission) {
                cameraPermission.launchPermissionRequest()
            }
    }
)

Since the state of the permission persists in the cameraPermission which uses rememberPermission function, it provides the following features

permission The permission to control and observe
hasPermissionIt is a boolean. When true, the user has granted permission
permissionRequestedIt is a boolean property. When true, the permission request has been done previously
shouldShowRationaleAgain a boolean property. When true, the user should be presented with a rationale
launchPermissionRequest( )This triggers a system dialog that asks the user to grant or revoke the permission

Here, as the icon button is clicked, it checks if the camera permission is granted.
1. If yes, then it launches the camera and loads the image.
2. If the permission is not granted, then it launches a permission request using launchPermissionRequest( ).

Permission dialog box prompted.

5. Finishing up

Finally, with all the logic in our hands, let’s wrap it up with our UI.

We have created another composable ScreenContentAccompanist( ) to build our UI. Here goes the code.

@OptIn(ExperimentalPermissionsApi::class)
@Composable
private fun ScreenContentAccompanist(
    modifier: Modifier,
    cameraPermission: PermissionState = cameraPermissionState(),
    resultBitmap: Bitmap?,
    launcherForImageCapture: ManagedActivityResultLauncher<Void?, Bitmap?>
) {
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = modifier.padding(20.dp)
    ) {
        Text(
            text = "Create your profile here!",
            style = TextStyle(
                fontWeight = FontWeight.Bold,
                fontSize = 20.sp
            )
        )
        Spacer(modifier = Modifier.height(24.dp))
        Divider(
            modifier = Modifier.fillMaxWidth(),
            color = Color.Gray
        )

        Spacer(modifier = Modifier.height(24.dp))
        Box(
            contentAlignment = Alignment.Center,
            modifier = Modifier
                .height(100.dp)
                .width(100.dp)
                .clip(shape = CircleShape)
                .border(
                    width = 1.dp,
                    color = Color.Gray,
                    shape = CircleShape
                )
        ) {
            resultBitmap?.asImageBitmap()?.let {
                Image(
                    bitmap = it,
                    contentDescription = "Captured image",
                    contentScale = ContentScale.Crop,
                    modifier = Modifier
                        .size(150.dp)
                        .clickable {
                            if (cameraPermission.hasPermission) {
                                launcherForImageCapture.launch()
                            } else if (!cameraPermission.hasPermission) {
                                cameraPermission.launchPermissionRequest()
                            }
                        }
                )
            }
        }
        Spacer(modifier = Modifier.height(16.dp))

        ProfileOutlinedTextField(value = "Name")
        ProfileOutlinedTextField(value = "Email")
        ProfileOutlinedTextField(value = "Password")
        ProfileOutlinedTextField(value = "DOB")

        Spacer(modifier = Modifier.height(24.dp))

        Button(
            onClick = { },
            modifier = Modifier
                .padding(horizontal = 36.dp)
                .fillMaxWidth()
        ) {
            Text(text = "Submit")
        }
    }
}

Nothing crazy here, we have just added a simple code to build a registration page UI.

The ProfileOutlinedTextField( ) is nothing but a simple OutlinedTextField( ) which looks like:

@Composable
fun ProfileOutlinedTextField(
    value: String,
    onValueChange: (String) -> Unit = { }
) {
    OutlinedTextField(
        value = value,
        onValueChange = onValueChange,
        modifier = Modifier.padding(vertical = 10.dp),
        textStyle = TextStyle(color = Color.Gray)
    )
}

Calling the ScreenContentAccompanist( ) with the appropriate parameters, completes our profile screen.

@ExperimentalPermissionsApi
@Composable
fun ProfileScreenWithAccompanist() {
    var resultBitmap: Bitmap? by rememberSaveable { mutableStateOf(placeHolderBitmap) }

    val launcherForImageCapture = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.TakePicturePreview()
    ) {
        resultBitmap = if (it.toString().isEmpty()) {
            placeHolderBitmap
        } else {
            it
        }
    }

    ScreenContentAccompanist(
        modifier = Modifier
            .fillMaxSize()
            .systemBarsPadding(),
        resultBitmap = resultBitmap,
        launcherForImageCapture = launcherForImageCapture
    )
}

Here is the working preview:

Working preview of the same.

6. Use Case – User keeps denying permissions

While working with an application that requires permission, you might encounter various situations. Here are some of them discussed below:

Permission might be denied and again while using the app, thus such situations are bound to happen:
1) In older android versions, permission denied twice, needs to be changed manually in the settings.
2) In newer android versions, permission can be granted only while using the app.

Project code and resources

Link to the code repository.

  1. Android Jetpack Compose – Android Documentation
  2. Android permissions documentation
  3. Manifest permissions documentation
  4. Accompanist library documentation
  5. Handling permissions without any library

I would also like to extend my gratitude to Raj for contributing clean, simple, and user-friendly code to this project.

Here We Go Again : (

if (article == helpful) {
    println("Like and subscribe to blog newsletter.")
} else {
    println("Let me know what i should blog on.")
}

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.