伴奏者如何正确处理jetpack中的运行时权限?

问题描述 投票:0回答:4

我正在使用 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()
    }

android kotlin android-jetpack-compose android-permissions
4个回答
2
投票

对于那些寻找类似场景的人。为了正确处理 jetpack compose 中的权限,我按照以下步骤操作:

  1. 点击按钮后,首先检查是否已授予权限。如果它已经被授予,那么只需做你需要做的工作。
  2. 如果未获得许可,我们将请求许可。在
    rememberPermissionState(){granted -> }
    的回调中,我们将检查是否授予了权限。
  3. 如果获得许可,那么只需做您需要做的工作即可。否则,我们现在有两种情况需要检查。检查
    shouldShowRequestPermissionRationale
    返回 true 还是 false。
  4. 由于我们已经请求了权限,所以我们已经忽略了在请求权限之前
    shouldShowRequestPermissionRationale
    第一次返回 false 的场景。
  5. 因此,如果
    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"


1
投票

我为此使用了 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
}

0
投票

这是完全符合您要求的代码:

点击按钮(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 权限的博客文章的一部分


0
投票
所有基于shouldShowRationale的答案都不会很好地工作,因为它们的实现在不同版本的Android上是不同的。根据时差检查我的实现:

https://stackoverflow.com/a/77027650/12544067

© www.soinside.com 2019 - 2024. All rights reserved.