我正在使用 accompanist 库来处理 jetpack compose 中的权限。文档中的示例代码没有处理权限的场景,例如检查按钮单击的权限。
所以我的场景是我想检查按钮单击的运行时权限,如果授予该权限,则执行所需的工作,或者如果未授予,则显示小吃栏。但我不知道如何检查权限是否被永久拒绝。
我想要像这个库一样的类似行为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)
)
}
}
}
这是按钮的代码,当我单击该按钮时我想检查权限
SecondaryOutlineButton(
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
buttonText = stringResource(
id = R.string.upload_image
),
buttonCornerRadius = 8.dp,
) {
readStoragePermissionState.launchPermissionRequest()
}
对于那些寻找类似场景的人。为了正确处理 jetpack compose 中的权限,我按照以下步骤操作:
rememberPermissionState(){granted -> }
的回调中,我们将检查是否授予了权限。shouldShowRequestPermissionRationale
返回 true 还是 false。shouldShowRequestPermissionRationale
第一次返回 false 的场景。shouldShowRequestPermissionRationale
返回 false,这意味着权限被永久拒绝,我们可以向用户显示一条消息,让其转到设置来授予权限,否则可能只会被拒绝一次,然后我们可以向用户显示一些消息,告诉用户原因我们需要获得该许可。
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val snackBarState = remember { SnackbarHostState() }
val getImageLauncher = rememberLauncherForActivityResult(
contract = GetContent()
) { uri ->
//Todo
}
// Remember Read Storage Permission State
val readStoragePermissionState = rememberPermissionState(
permission = READ_EXTERNAL_STORAGE
) { granted ->
if (granted) {
getImageLauncher.launch("image/*")
} else {
context.findActivity()?.apply {
when {
shouldShowRationale(READ_EXTERNAL_STORAGE) -> {
snackbarState.showSnackBar(
message = context.getString(
R.string.read_storage_rational
),
coroutineScope = coroutineScope,
)
}
else -> {
snackbarState.showSnackBar(
action = context.getString(
R.string.settings
),
message = context.getString(
R.string.read_storage_denied
),
coroutineScope = coroutineScope,
onSnackBarAction = {
context.gotoApplicationSettings()
},
)
}
}
}
}
}
可组合按钮
SecondaryOutlineButton(
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
buttonText = stringResource(
id = R.string.upload_image
),
buttonCornerRadius = 8.dp,
) {
if (context.hasPickMediaPermission()) {
launcher.launch(
getImageLauncher.launch("image/*")
)
} else {
permission.launchPermissionRequest()
}
}
扩展功能
fun Context.isPermissionGranted(name: String): Boolean {
return ContextCompat.checkSelfPermission(
this, name
) == PackageManager.PERMISSION_GRANTED
}
fun Activity.shouldShowRationale(name: String): Boolean {
return shouldShowRequestPermissionRationale(name)
}
fun Context.hasPickMediaPermission(): Boolean {
return when {
// If Android Version is Greater than Android Pie!
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> true
else -> isPermissionGranted(name = READ_EXTERNAL_STORAGE)
}
}
fun Context.gotoApplicationSettings() {
startActivity(Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.parse("package:${packageName}")
})
}
fun Context.findActivity(): Activity? {
return when (this) {
is Activity -> this
is ContextWrapper -> {
baseContext.findActivity()
}
else -> null
}
}
fun SnackbarHostState.showSnackBar(
message: String? = null,
action: String? = null,
duration: SnackbarDuration = Short,
coroutineScope: CoroutineScope,
onSnackBarAction: () -> Unit = {},
onSnackBarDismiss: () -> Unit = {},
) {
if (!message.isNullOrEmpty()) {
coroutineScope.launch {
when (showSnackbar(
message = message,
duration = duration,
actionLabel = action,
withDismissAction = duration == Indefinite,
)) {
SnackbarResult.Dismissed -> onSnackBarDismiss.invoke()
SnackbarResult.ActionPerformed -> onSnackBarAction.invoke()
}
}
}
}
我正在使用
implementation "com.google.accompanist:accompanist-permissions:0.25.0"
我为此使用了 Philipp Lackner 的 tutorial。他创建了一个扩展方法,以防权限被永久拒绝。
因此,在您的按钮代码中,您将有一个执行此操作的方法:
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.")
}
}
}
扩展名是:
@ExperimentalPermissionsApi
fun PermissionState.isPermanentlyDenied(): Boolean {
return !status.shouldShowRationale && !status.isGranted
}
这是完全符合您要求的代码:
点击按钮(FAB),如果已经授予权限,则开始工作。如果未授予权限,请在请求之前检查我们是否需要向用户显示更多信息(shouldShowRationale),并在需要时显示 SnackBar。否则,只需请求许可(如果获得许可,则开始工作)。
请记住,无法再检查权限是否被永久拒绝。
shouldShowRationale()
在不同版本的 Android 中工作方式有所不同。相反,您可以做的(参见代码)是,如果 shouldShowRationale()
返回 true,则显示您的 SnackBar。
@Composable
fun OptionalPermissionScreen() {
val context = LocalContext.current.applicationContext
val state = rememberPermissionState(Manifest.permission.CAMERA)
val scaffoldState = rememberScaffoldState()
val launcher = rememberLauncherForActivityResult(RequestPermission()) { wasGranted ->
if (wasGranted) {
// TODO do work (ie forward to viewmodel)
Toast.makeText(context, "📸 Photo in 3..2..1", Toast.LENGTH_SHORT).show()
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
scaffoldState = scaffoldState,
floatingActionButton = {
val scope = rememberCoroutineScope()
val snackbarHostState = scaffoldState.snackbarHostState
FloatingActionButton(onClick = {
when (state.status) {
PermissionStatus.Granted -> {
// TODO do work (ie forward to viewmodel)
Toast.makeText(context, "📸 Photo in 3..2..1", Toast.LENGTH_SHORT).show()
}
else -> {
if (state.status.shouldShowRationale) {
scope.launch {
val result =
snackbarHostState.showSnackbar(
message = "Permission required",
actionLabel = "Go to settings"
)
if (result == SnackbarResult.ActionPerformed) {
val intent = Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", context.packageName, null)
)
startActivity(intent)
}
}
} else {
launcher.launch(Manifest.permission.CAMERA)
}
}
}
}) {
Icon(Icons.Rounded.Camera, contentDescription = null)
}
}) {
// the rest of your screen
}
}
有关其工作原理的视频点击此处。
这是我撰写的有关 Jetpack Compose 权限的博客文章的一部分。