Home > database >  How to handle runtime permissions in jetpack compose properly by accompanist?
How to handle runtime permissions in jetpack compose properly by accompanist?

Time:10-01

I'm using the accompanist library for handling permissions in jetpack compose. The sample code in the docs doesn't have a scenario to handle permissions such as checking permission on button clicks.

So My scenario is I wanted to check runtime permission on the button click and if the permission is granted do the required work or show the snackbar if not granted. But I can't figure out how can i check if permission was denied permanently or not.

I want a similar behavior like this library has https://github.com/Karumi/Dexter

    val getImageLauncher = rememberLauncherForActivityResult(
        contract = GetContent()
    ) { uri ->

        uri?.let {
            viewModel.imagePicked.value = it.toString()
        }
    }

    // Remember Read Storage Permission State
    val readStoragePermissionState = rememberPermissionState(
        permission = READ_EXTERNAL_STORAGE
    ) { result ->

        if (result) {
            getImageLauncher.launch("image/*")
        } else {

            // How can i check here if permission permanently denied?
            
            coroutineScope.launch {

                scaffoldState.snackbarHostState.showSnackbar(
                    context.getString(R.string.read_storage_denied)
                )
                
            }
        }
    }

Here's the code of the button on which when I click I want to check the permission

    SecondaryOutlineButton(
        modifier = Modifier
            .fillMaxWidth()
            .height(48.dp),
        buttonText = stringResource(
            id = R.string.upload_image
        ),
        buttonCornerRadius = 8.dp,
    ) {
        readStoragePermissionState.launchPermissionRequest()
    }

CodePudding user response:

I used Philipp Lackner's tutorial for this. He creates an extension method in case the permission is permanently denied.

So in your button Code you would have a method doing this:

Manifest.permission.CAMERA -> {
                    when {
                        perm.status.isGranted -> {                                                                 
                          PermissionText(text = "Camera permission accepted.")
                          }

                        perm.status.shouldShowRationale -> {
                            PermissionText(text = "Camera permission is needed to take pictures.")
                        }

                        perm.isPermanentlyDenied() -> {
                            PermissionText(text = "Camera permission was permanently denied. You can enable it in the app settings.")
                        }
                    }
                }

And the extension would be:

@ExperimentalPermissionsApi
fun PermissionState.isPermanentlyDenied(): Boolean {
    return !status.shouldShowRationale && !status.isGranted
}

CodePudding user response:

For those looking for a similar scenario. To handle permissions in jetpack compose properly I followed the below steps:

  1. When the button is clicked first check if the permission is already granted. If it's already granted then simply do the work you needed to do.

  2. If it's not granted we will check the case for shouldShouldRational is false. If it's false we have two scenarios to check because the shouldShowRationale is false in two cases. First when the permission is permanently denied. Second when permission is not even asked at once. For managing, if permission is permanently denied or not I have used shared preferences. I have written extension functions for that which tell us if the permission is asked for once.

  3. For the above first case, I'll show the snack bar telling the user that you permanently denied the permission open settings to allow the permission. For the above second case, I will launch the request for showing the system permission dialog and update the shared preference via the extension function.

  4. And for the case in which shouldShowRationale is true. I'll show a snack bar to the user explaining why permission is required. Along with the action, to again request the system permission dialog.

  5. Finally whenever permission is granted I can do the work needed in the rememberPermissionState callback.


val context = LocalContext.current

val scaffoldState = rememberScaffoldState()

val coroutineScope = rememberCoroutineScope()

val getImageLauncher = rememberLauncherForActivityResult(
    contract = GetContent()
) { uri ->

    uri?.let {
        viewModel.imagePicked.value = it.toString()
    }
}

// Remember Read Storage Permission State
val readStoragePermissionState = rememberPermissionState(
    permission = READ_EXTERNAL_STORAGE
) { granted ->

    if (granted) {
        getImageLauncher.launch("image/*")
    }
}

Button Composable


SecondaryOutlineButton(
    modifier = Modifier
        .fillMaxWidth()
        .height(48.dp),
    buttonText = stringResource(
        id = R.string.upload_image
    ),
    buttonCornerRadius = 8.dp,
) {
    // This is onClick Callback of My Custom Composable Button
    with(readStoragePermissionState) {

        when {

            // If Permission is Already Granted to the Application
            status.isGranted -> {
                getImageLauncher.launch("image/*")
            }

            // If Permission is Asked First or Denied Permanently
            !status.shouldShowRationale -> {

                context.isPermissionAskedForFirstTime(
                    permission = permission
                ).also { result ->

                    if (result) {

                        launchPermissionRequest()

                        context.permissionAskedForFirsTime(
                            permission = permission
                        )

                    } else {

                        coroutineScope.launch {

                            with(scaffoldState.snackbarHostState) {

                                val snackbarResult =
                                    showSnackbar(
                                        message = context.getString(
                                            R.string.read_storage_denied
                                        ),
                                        actionLabel = context.getString(
                                            R.string.settings
                                        )
                                    )

                                when (snackbarResult) {
                                    // Open this Application General Settings.
                                    SnackbarResult.ActionPerformed -> {
                                        context.openApplicationSettings()
                                    }

                                    SnackbarResult.Dismissed -> Unit
                                }
                            }
                        }
                    }
                }
            }

            // If You should Tell User Why this Permission Required
            status.shouldShowRationale -> {

                coroutineScope.launch {

                    with(scaffoldState.snackbarHostState) {

                        val snackbarResult = showSnackbar(
                            message = context.getString(
                                R.string.read_storage_rational
                            ),
                            actionLabel = context.getString(
                                R.string.allow
                            )
                        )

                        when (snackbarResult) {
                            // Request for System Permission Dialog Again.
                            SnackbarResult.ActionPerformed -> {
                                launchPermissionRequest()
                            }

                            SnackbarResult.Dismissed -> Unit
                        }
                    }
                }
            }

            else -> Unit
        }
    }
}

Extension Functions

fun Context.isPermissionAskedForFirstTime(
    permission: String
): Boolean {

    return getSharedPreferences(
        packageName, MODE_PRIVATE
    ).getBoolean(permission, true)
}

fun Context.permissionAskedForFirsTime(
    permission: String
) {
    getSharedPreferences(
        packageName, MODE_PRIVATE
    ).edit().putBoolean(permission, false).apply()
}

fun Context.openApplicationSettings() {
    startActivity(Intent().apply {
        action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
        data = Uri.parse("package:${packageName}")
    })
}

I'm using implementation "com.google.accompanist:accompanist-permissions:0.25.0"

  • Related