How one can Request Android Runtime Permissions the use of Jetpack Compose?

There are 2 forms of permissions:

  • Set up-time permissions

  • Runtime permissions

An instance of the commonest install-time permissions is web permissions. The Android OS device routinely grants your app the permission when the app is put in. You simply wish to claim the permission within the AndroidManifest.xml.

In contrast to runtime permissions, along with stating the permissions within the AndroidManifest.xml, you additionally wish to manually request the permissions to your app. There are some gotchas on soliciting for runtime permissions, which shall be mentioned later.

Why Accompanist Permissions?

After taking part in round with other Android permission implementations in Jetpack Compose, I favor to make use of Accompanist permissions library over rememberLauncherForActivityResult() for the next causes:

  • Permissions state checking is moderately more straightforward the use of rememberPermissionState() or rememberMultiplePermissionsState() as an alternative of looking ahead to callback effects from the task end result launcher.

  • Gaining access to to shouldShowRequestPermissionRationale() in composable serve as isn’t that instantly ahead. You almost certainly need to override the shouldShowRequestPermissionRationale() in Task and do one thing like this, and determine how this can also be accessed out of your composable serve as.

      elegance MainActivity : ComponentActivity() {
          
          override amusing shouldShowRequestPermissionRationale(
              permission: String) : Boolean 
          {
              go back if(Construct.VERSION.SDK_INT >= Construct.VERSION_CODES.M) {
                  tremendous.shouldShowRequestPermissionRationale(permission)
              } else {
                  false
              }
          }
      }
    

    In Accompanist permission, you’ll be able to simply get entry to the PermissionStatus.shouldShowRationale variable from PermissionState.

Request Runtime Permission Gotchas

Permission Request Conversation is Dismissible

Request runtime permission lets in person to brush aside the permission conversation in sure Android model (I believe is ranging from Android 11/ API degree 30). You’ll be able to both click on outdoor the permission request conversation or just press the again button to brush aside the permission request conversation.

The issue is, when the permission conversation is brushed aside, the PermissionState knowledge stays unchanged. So we haven’t any thought, the person has brushed aside the permission request conversation.

What I did to workaround with this factor is I release the permission request on most sensible of every other OptionalLaunchPermissionDialog() which is the wrapper for AlertDialog().

@OptIn(ExperimentalPermissionsApi::elegance)
@Composable
amusing OptionalSinglePermissionScreen(
    permission: String,
) {
    
    OptionalLaunchPermissionDialog(
        permission,
        permissionState,
        dismissCallback = { launchPermissionDialog = false}
    )

    SideEffect {
        permissionState.launchPermissionRequest()
    }        
}

So, when the permission request is brushed aside, the AlertDialog() is proven to permit person to relaunch or cancel the permission request (if the permission request is non-compulsory).

Do not Release Permission Request when shouldShowRationale is True

When shouldShowRationale is True, it manner the permission has been denied earlier than. The launchPermissionRequest() used to be first referred to as earlier than. Should you name launchPermissionRequest() the second one time, it nonetheless works. Alternatively, if you happen to deny the permission this time, the shouldShowRationale is now set to false. You name launchPermissionRequest(), not anything will occur.

So after this degree, you utterly misplaced the permission state standing. You have no idea the runtime permission request has been completely denied. So, the most efficient apply right here for my part is to turn the rational UI to lead person to allow the permission manually when shouldShowRationale is about to True.

@OptIn(ExperimentalPermissionsApi::elegance)
@Composable
amusing OptionalSinglePermissionScreen(
    permission: String,
) {
    val permissionState = rememberPermissionState(permission)

    if (permissionState.standing.isGranted) {
        
    } else if (permissionState.standing.shouldShowRationale) {
        
    } else {
        
    }
}

Request Runtime Permission App Demo

This demo app has the next examples:

  • Non-compulsory Unmarried Runtime Permission Request

  • Required Unmarried Runtime Permission Request

  • Non-compulsory More than one Runtime Permissions Request

  • Required More than one Runtime Permissions Request

The step by step information right here refers to Non-compulsory Unmarried Runtime Permission Request. For the remainder, you’ll be able to seek advice from the supply code.

1. Claim Permission in Your Manifest Record

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:title="android.permission.CALL_PHONE" />
    
</manifest>

2. Upload Accompanist Permissions Dependency

dependencies {
    
    implementation ("com.google.accompanist:accompanist-permissions:0.31.0-alpha")
}

3. Put in force Permission Request Good judgment

@OptIn(ExperimentalPermissionsApi::elegance)
@Composable
amusing OptionalSinglePermissionScreen(
    permission: String,
) {
    var permissionStatusText via be mindful { mutableStateOf("") }
    val permissionState = rememberPermissionState(permission)

    var launchPermissionDialog via be mindful { mutableStateOf(true) }
    var showRationale via be mindful { mutableStateOf(true) }

    if (permissionState.standing.isGranted) {
        permissionStatusText = "Granted"
    }

    else if (permissionState.standing.shouldShowRationale) {
        permissionStatusText = "Denied"

        if(showRationale) {
            OptionalRationalPermissionDialog(
                permission,
                dismissCallback = {showRationale = false}
            )
        }

    } else {
        permissionStatusText = "N/A"
        if (launchPermissionDialog) {
            OptionalLaunchPermissionDialog(
                permission,
                permissionState,
                dismissCallback = { launchPermissionDialog = false}
            )

            SideEffect {
                permissionState.launchPermissionRequest()
            }
        }

    }

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Association.Middle,
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Textual content("Non-compulsory Permission standing: $permissionStatusText")
    }
}

That is the OptionalLaunchPermissionDialog() that permits you to relaunch the permission request when the permission request conversation is brushed aside.

@OptIn(ExperimentalPermissionsApi::elegance)
@Composable
amusing OptionalLaunchPermissionDialog (
    permission: String,
    permissionState: PermissionState,
    dismissCallback: () -> Unit
) {
    val context = LocalContext.present
    val permissionLabel = stringResource(
        context.packageManager.getPermissionInfo(permission, 0).labelRes
    )

    AlertDialog(
        onDismissRequest = { dismissCallback()},
        identify = { Textual content(textual content = "Permission Required!") },
        textual content = { Textual content(textual content = permissionLabel) },
        confirmButton = {
            Button(onClick = {
                permissionState.launchPermissionRequest()
            }) {
                Textual content(textual content = "Release")
            }
        },
        dismissButton = {
            Button(onClick = {
                dismissCallback()
            }) {
                Textual content(textual content = "Cancel")
            }
        }
    )
}

  • This stringResource(context.packageManager.getPermissionInfo(permission, 0).labelRes) supplies a extra user-friendly permission string.

  • Since that is an non-compulsory permission request, we permit the person to brush aside this conversation.

OptionalRationalPermissionDialog() presentations the the explanation why the app wishes the permission and information the person to grant the permission manually.

@Composable
amusing OptionalRationalPermissionDialog (
    permission: String,
    dismissCallback: () -> Unit
) {
    val context = LocalContext.present
    val permissionLabel = stringResource(
        context.packageManager.getPermissionInfo(permission, 0).labelRes
    )

    AlertDialog(
        onDismissRequest = { dismissCallback()},
        identify = { Textual content(textual content = "Permission Required!") },
        textual content = { Textual content(textual content = permissionLabel) },
        confirmButton = {
            Button(onClick = {
                val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                    .practice {
                        knowledge = Uri.fromParts("package deal", context.packageName, null)
                    }
                ContextCompat.startActivity(context, intent, null)
            }) {
                Textual content(textual content = "Move to settings")
            }
        },
        dismissButton = {
            Button(onClick = {
                dismissCallback()
            }) {
                Textual content(textual content = "Cancel")
            }
        }
    )
}

This creates an task intent that brings you to the app atmosphere which you manually grant the permission on your app.

val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
    .practice {
        knowledge = Uri.fromParts("package deal", context.packageName, null)
    }
ContextCompat.startActivity(context, intent, null)

Smartly, this is it!

4. Revoke Runtime Permissions

For checking out, we continuously need to revoke the runtime permissions and rerun the app once more. So I gave this ADB command a check out.

 adb shell pm revoke vtsen.hashnode.dev.runtimepermissiondemoapp android.permission.CALL_PHONE

It does take away the permission, but it surely has an surprising conduct. The shouldShowRationale is now set to True. I need it to be False. To totally revoke the runtime permissions, I both wish to reinstall the app or transparent the app garage.

Conclusion

More than one permissions request is the same. As a substitute of getting unmarried PermissionState, it has Record<PermissionState>. I can most certainly use it via default in my app as a result of it might additionally improve unmarried permission request. Final however no longer least, when shouldShowRationale is True, do NOT release the runtime permission request.

Supply Code

GitHub Repository: Demo_RuntimePermission

You May Also Like

More From Author

+ There are no comments

Add yours