【问题标题】:How to handle runtime permissions in jetpack compose properly by accompanist?伴奏者如何正确处理 jetpack compose 中的运行时权限?
【发布时间】:2023-01-22 11:09:37
【问题描述】:

我正在使用伴奏库来处理 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


    【解决方案1】:

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

    1. 单击按钮时,首先检查是否已授予权限。如果它已经被授予,那么只需做你需要做的工作。

    2. 如果未被授予,我们将检查 shouldShouldRational 是否为假的情况。如果它为假,我们有两种情况需要检查,因为在两种情况下 shouldShowRationale 为假。首先是当权限被永久拒绝时。其次,甚至没有立即征求许可。对于管理,如果权限被永久拒绝,我使用了共享首选项。我已经为此编写了扩展函数,它告诉我们是否请求一次许可。

    3. 对于上面的第一种情况,我会显示小吃店告诉用户你永久拒绝了权限打开设置允许权限。对于上述第二种情况,我将发起显示系统权限对话框的请求,并通过扩展功能更新共享首选项。

    4. 对于 shouldShowRationale 为真的情况。我将向用户展示一个小吃店,解释为什么需要许可。随着操作的进行,再次请求系统权限对话框。

    5. 最后,无论何时授予权限,我都可以在 rememberPermissionState 回调中完成所需的工作。

      
      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/*")
          }
      }
      
      

      按钮可组合

      
      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
              }
          }
      }
      
      

      扩展功能

      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}")
          })
      }
      

      我正在使用implementation "com.google.accompanist:accompanist-permissions:0.25.0"

    【讨论】:

    • 不过,您可能需要考虑系统重置权限的情况。在那种情况下,您存储的首选项不同步。相反:当 showRationale 为 false 时,始终尝试请求一次许可。当 showRationale 仍然为 false 时,您知道该权限已被永久拒绝。
    • @Uli 你能提供任何sn-ps吗?
    • 您可以使用类似这样的方法来跟踪权限请求计数,但仅使用布尔值来跟踪第一个权限请求与以后的权限请求可能就足够了:github.com/google/accompanist/issues/…
    【解决方案2】:

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

    【讨论】:

    • 我已经看过那个视频了,但是这个条件是错误的,因为 shouldShowRationale 第一次总是错误的。
    • 此外,我的 UI 流程与此略有不同!
    【解决方案3】:

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

    单击一个按钮(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
        }
    }
    
    

    这是如何工作的视频by clicking here

    这是blog post I wrote on permissions in Jetpack Compose 的一部分。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-01-18
      • 2022-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多