Jetpack Compose新增mutableStateOf后权限回调触发两次的原因及解决方法
问题触发原因
- 权限申请逻辑直接写在Composable的重组流程中,未做触发次数限制:每次
DisplayPermissionDialog重组时都会重新检查权限,只要权限未授予,就会通过Handler.post再次调用launcher.launch发起权限申请。 - 新增
displayEmptyScreen后,拒绝权限时对这个MutableState赋值会触发当前Composable重组,此时权限仍为拒绝状态,就会二次发起权限申请,导致回调执行两次、日志打印两次。 - 最初没有修改
displayEmptyScreen的逻辑时,拒绝权限不会触发当前Composable重组,也就不会二次发起申请,因此日志仅打印一次。
额外说明:你当前的权限判断逻辑
permissions.values.first { true }存在问题,该写法等价于直接取第一个权限的授权结果,若你申请了多个权限,判断会不准确,建议改为permissions.values.all { it }来校验所有申请的权限都已授权。
修复方案
你可以通过限制权限申请的触发次数解决问题,推荐将权限申请逻辑放到仅执行一次的LaunchedEffect中,避免每次重组都重复发起申请,修改后代码如下:
@Composable private fun DisplayPermissionDialog(shouldDisplayQuestionDialog: MutableState<Boolean>) { val displayEmptyScreen = remember { mutableStateOf(false) } val launcher = rememberLauncherForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { permissions -> // 修复权限判断逻辑:所有权限都授予才走成功分支 if (permissions.values.all { it }) { log.info("Permission is granted") shouldDisplayQuestionDialog.value = true } else { log.info("Permission is not granted") displayEmptyScreen.value = true } } DisplayQuestionAlertDialog(shouldDisplayQuestionDialog) if (displayEmptyScreen.value) { Box { // 背景层 Box( modifier = Modifier .matchParentSize() .background(Color.White) ) } } val context = LocalContext.current // 权限检查和申请逻辑放到LaunchedEffect中,仅组件首次加载时执行一次 LaunchedEffect(Unit) { when (PackageManager.PERMISSION_GRANTED) { ContextCompat.checkSelfPermission( context, Manifest.permission.READ_EXTERNAL_STORAGE ) -> { log.info("Set State for mutableStateFlow") viewModel.setState() DisplayQuestionAlertDialog(shouldDisplayQuestionDialog) } else -> { // LaunchedEffect默认在主线程执行,无需额外通过Handler.post切换 launcher.launch( arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) ) } } } }
如果你不想用LaunchedEffect,也可以新增一个标记位限制申请次数:
// 新增标记位记录是否已经发起过权限申请 val hasRequestedPermission = remember { mutableStateOf(false) } val context = LocalContext.current when (PackageManager.PERMISSION_GRANTED) { ContextCompat.checkSelfPermission( context, Manifest.permission.READ_EXTERNAL_STORAGE ) -> { LaunchedEffect(Unit) { log.info("Set State for mutableStateFlow") viewModel.setState() } DisplayQuestionAlertDialog(shouldDisplayQuestionDialog) } else -> { // 仅未发起过申请时才调用launch if (!hasRequestedPermission.value) { hasRequestedPermission.value = true Handler(Looper.getMainLooper()).post { launcher.launch( arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) ) } } } }
内容的提问来源于stack exchange,提问作者Ali
相关产品推荐
相关产品推荐

