Skip to content

Instantly share code, notes, and snippets.

@erfansn
Last active August 8, 2023 11:33
Show Gist options
  • Save erfansn/24f86473af544951bef23532ee3b53c6 to your computer and use it in GitHub Desktop.
Save erfansn/24f86473af544951bef23532ee3b53c6 to your computer and use it in GitHub Desktop.
Convenient handling of runtime permissions in Jetpack Compose
@Composable
fun PermissionsRequestButton(
permissions: List<String>,
onGranted: () -> Unit,
modifier: Modifier = Modifier,
onRationaleShow: (List<String>) -> Unit = { },
onPermanentlyDenied: (List<String>) -> Unit = { },
onPartiallyGranted: (List<String>) -> Unit = { },
content: @Composable RowScope.() -> Unit,
) {
val permissionsRequest = rememberPermissionsRequestLauncher(
onGranted = onGranted,
onRationaleShow = onRationaleShow,
onPermanentlyDenied = onPermanentlyDenied,
onPartiallyGranted = onPartiallyGranted,
)
Button(
modifier = modifier,
onClick = { permissionsRequest.launch(permissions.toTypedArray()) },
content = content,
)
}
@Composable
fun rememberPermissionsRequestLauncher(
onGranted: () -> Unit,
onRationaleShow: (permissions: List<String>) -> Unit = { },
onPermanentlyDenied: (permissions: List<String>) -> Unit = { },
onPartiallyGranted: (permissions: List<String>) -> Unit = { },
): ManagedActivityResultLauncher<Array<String>, *> {
val activity = LocalContext.current.findActivity()
val permissionsStatusDetermined = remember {
activity.getSharedPreferences("permissions_status_determined", Context.MODE_PRIVATE)
}
operator fun SharedPreferences.set(keys: List<String>, value: Boolean) = edit(commit = true) {
keys.forEach { putBoolean(it, value) }
}
operator fun SharedPreferences.get(keys: List<String>, default: Boolean = false) =
keys.isNotEmpty() && keys.all { getBoolean(it, default) }
return rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestMultiplePermissions(),
) { permissionsResult ->
val result = permissionsResult.mapValues { (_, isGranted) -> PermissionStatus(isGranted) }
val permissions = result.keys.toList()
val permissionsStatus = result.values.toList()
if (result.isNotEmpty() && permissions.size > 1 && permissionsStatus.count(PermissionStatus::isGranted) in 1..<permissions.size) {
onPartiallyGranted(result.filterValues { it.isGranted }.map { it.key })
}
when {
permissions.any {
ActivityCompat.shouldShowRequestPermissionRationale(activity, it)
} -> {
permissionsStatusDetermined[permissions] = true
onRationaleShow(permissions.filter { ActivityCompat.shouldShowRequestPermissionRationale(activity, it) })
}
permissionsStatusDetermined[permissions] && permissionsStatus.any {
!it.isGranted
} -> {
permissionsStatusDetermined[permissions] = true
onPermanentlyDenied(result.filterValues { !it.isGranted }.map { it.key })
}
// When requests are repeatedly sent the result maybe be empty
permissionsStatus.isNotEmpty() && permissionsStatus.all(PermissionStatus::isGranted) -> {
permissionsStatusDetermined[permissions] = true
onGranted()
}
}
}
}
@JvmInline
value class PermissionStatus(val isGranted: Boolean)
private tailrec fun Context.findActivity(): Activity = when (this) {
is Activity -> this
is ContextWrapper -> baseContext.findActivity()
else -> throw IllegalStateException()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment