Android/권한(permissions)

앱 권한 구현

sryang 2025. 5. 22. 16:42

권한을 학습하고 구현하는데 다름 꽤 시간을 사용했다.

 

아직 더 봐야하지만 문서만 보기 지쳐 구현을 시도해봤다.

 

모듈로 만들어 구현했다.

dependencies {
    implementation 'com.github.sarang628:ComposePermissionTest:최신커밋'
}

 

 

기본 구현 프로세스를 코드로 정리해봤다.

when 구문에 있는 내용을 보면 대략적으로 프로세스를 이해할 수 있다.

권한 처리 절차는 따로 모아서 보고싶어 ViewModel에서 구현 했다.

@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class)
@Composable
fun WorkFlowImpl(
    viewModel: BestPracticeViewModel = BestPracticeViewModel(),
    permission : String = Manifest.permission.ACCESS_FINE_LOCATION
) {
    var timeDiff : Long by remember { mutableStateOf(0L) } // 영구 권한 거부 상태 체크를 위한 시간
    val requestPermission = rememberPermissionState(permission, { viewModel.permissionResult(it, System.currentTimeMillis() - timeDiff); })
    val state = viewModel.state
    var stateTxt by remember { mutableStateOf("RequestPermission") }

    when (state) {
        InitialPermissionCheck  /* 최초 권한 체크 */ -> { viewModel.initialPermissionCheck(requestPermission.status.isGranted) }
        RecognizeToUser         /* UX에 권한을 필요로 하는 정보 인지 시키기 */-> { DescribePermissionDialog(onYes = { viewModel.yesInRecognizeUser() }, onNo = { viewModel.noInRecognizeUser() }) }
        CheckRational           /* rational 여부 확인 */-> { viewModel.checkRational(requestPermission.status.shouldShowRationale) }
        DeniedPermission        /* 권한 거부 */-> { stateTxt = "권한을 거부함." }
        GrantedPermission       /* 사용자가 권한을 허가했다면, 자원 접근 가능 */-> { stateTxt = "권한을 허용함." }
        RequestPermission       /* 런타임 권한 요청하기 */ -> { LaunchedEffect(state == RequestPermission) { requestPermission.launchPermissionRequest(); timeDiff = System.currentTimeMillis() } }
        SuggestSystemSetting    /* 권한 거부 상태에서 요청 시 */ -> { MoveSystemSettingDialog(onMove = { viewModel.onMoveInSystemDialog() }, onDeny = {viewModel.noInSystemDialog()}) }
        ShowRationale           /* rationale을 표시 */ -> { RationaleDialog({ viewModel.yesRationale() }, {viewModel.noRationale()}) }
    }

    Column {
        Text(state.toString().split("$")[1].split("@")[0])
        MyLocation(hasPermission = requestPermission.status.isGranted, 0, onRequestPermission = {
            viewModel.request()
        })
    }
}

 

1. 최초 권한 체크

 

권한 유무를 체크하여 5.DeniedPermission 또는 6.GrantedPermission 으로 상태를 변경

InitialPermissionCheck  /* 최초 권한 체크 */ -> { 
    viewModel.initialPermissionCheck(requestPermission.status.isGranted) 
}

 

이벤트에 대한 뷰모델 처리 이다.

/**
 * 최초 권한 체크
 * @param isGranted 권한 허용 여부
 * @return GrantedPermission(허용 시) or DeniedPermission(그 외)
 */
fun initialPermissionCheck(isGranted: Boolean) {
    viewModelScope.launch {
        delay(1)
        state = if (isGranted) GrantedPermission else DeniedPermission
    }
}

 

 

2. UX에 권한을 필요로 하는 정보 인지 시키기

 

화면 다이얼로그는 아래같이 처리했다.

RecognizeToUser /* UX에 권한을 필요로 하는 정보 인지 시키기 */-> { 
DescribePermissionDialog(onYes = { viewModel.yesInRecognizeUser() }, onNo = { viewModel.noInRecognizeUser() }) 
}

 

뷰모델에서 다이얼로그 이벤트 따른 다음순서이다. (이렇게 단순한데 로직을 나눌필요 있을까 싶지만. 모르겠다. 이렇게 하고싶었다.)

/** 사용자 알림 화면에서 Yes */
fun yesInRecognizeUser() { state = CheckRationale }

/** 사용자 알림 화면에서 No */
fun noInRecognizeUser() { state = InitialPermissionCheck }

 

 

3. Rational 여부 확인

권한을 요청하면 요청 전 Rationale(권한이 필요한 이유) 보여줘야 하는지 체크한다.

CheckRational           /* rational 여부 확인 */-> { viewModel.checkRational(requestPermission.status.shouldShowRationale) }
ShowRationale           /* rationale을 표시 */ -> { RationaleDialog({ viewModel.yesRationale() }, {viewModel.noRationale()}) }

 

4. 권한 요청하기

timeDiff는 권한을 2번 거부하면, 요청 하더라도 바로 거부로 떨어져 시간을 체크해 영구 거부 여부를 확인 용도이다.

RequestPermission       /* 런타임 권한 요청하기 */ -> { LaunchedEffect(state == RequestPermission) { requestPermission.launchPermissionRequest(); timeDiff = System.currentTimeMillis() } }

 

5. 시스템 권한 화면 이동

 

2회 이상 권한을 거부하면, 다음 권한 요청시 SuggestSystemSetting 상태로 변경 됨.
이 상태에서 아래와 같은 다이얼로그를 띄워준다.

 

화면 이동 여부 사용자 액션에 대해 이벤트를 설정 해줘야 함. viewModel.onMoveInSystemDialog(), viewModel.noInSystemDialog()

SuggestSystemSetting    /* 권한 거부 상태에서 요청 시 */ -> { MoveSystemSettingDialog(onMove = { viewModel.onMoveInSystemDialog() }, onDeny = {viewModel.noInSystemDialog()}) }

 

6. (추가) onStart 시 권한 다시 체크하기

// 앱이 포그라운드로 복귀했을 때 실행
DisposableEffect(lifecycleOwner) {
    val observer = LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_START) {
            viewModel.onStart()
        }
    }

    lifecycleOwner.lifecycle.addObserver(observer)

    onDispose {
        lifecycleOwner.lifecycle.removeObserver(observer)
    }
}

 

어려운듯. 할만 한 듯. 까다로운 권한 체크 프로세스 이다. private dashboard나 앱 권한 페이지에 처리하는 기능도 있어 이 부분도 추후 구현해보겠다.

 

https://github.com/sarang628/ComposePermissionTest

 

GitHub - sarang628/ComposePermissionTest

Contribute to sarang628/ComposePermissionTest development by creating an account on GitHub.

github.com