관리 메뉴

나만을 위한 블로그

[Android] 라이브러리 없이 권한 요청하는 법 본문

Android

[Android] 라이브러리 없이 권한 요청하는 법

참깨빵위에참깨빵 2023. 8. 28. 22:05
728x90
반응형

라이브러리를 사용할 수 없는 때가 오면 순수 로직으로 커버쳐야 할 때가 언젠가 발생할 수 있다. 라이브러리에 의존하기 쉬운 것 중 하나가 권한인데, 이 포스팅에선 라이브러리 없이 권한을 요청하고 그에 따라 텍스트뷰, 이미지뷰의 글자, 이미지를 각각 바꾸는 예제를 포스팅한다.

데이터 바인딩과 hilt, StateFlow를 사용해서 예제를 구성해 봤다. 예제에 너무 과한 스펙을 입히는 것 같긴 한데, 내 블로그에 쓰는 포스팅이니 내 맘대로 할란다. 나만을 위한 블로그니까

 

먼저 drawable 패키지에 allowed, not_allowed라는 이름의 아무 이미지나 추가한다. 대충 권한이 허용됐구나, 안 됐구나를 알 수 있는 간단한 드로어블이면 상관없다.

그리고 뷰모델을 간단하게 구성한다.

 

import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject

@HiltViewModel
class PermissionRequestViewModel @Inject constructor() : ViewModel() {

    // 권한 상태를 나타내는 StateFlow
    private var _permissionState = MutableStateFlow(false)
    val permissionState: StateFlow<Boolean> = _permissionState

    /**
     * 권한 허용 여부를 T/F로 갖는 StateFlow를 업데이트하는 함수
     * 
     * @param isAllowed 권한 허용 여부
     */
    fun updatePermissionState(isAllowed: Boolean) {
        _permissionState.value = isAllowed
    }
}

 

hilt를 썼기 때문에 뷰모델에 써야 하는 어노테이션 쓰고, MutableStateFlow, StateFlow 프로퍼티를 각각 선언하고 액티비티에서 호출할 함수만 존재하는 간단한 뷰모델이다. 코드만 달랑 쓰자니 너무 허전해서 주석이라도 좀 추가해봤다.

아래는 액티비티의 전체 코드다.

 

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".permission.PermissionRequestActivity">

        <ImageView
            android:id="@+id/ivPermissionState"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:src="@drawable/not_allowed"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>

        <TextView
            android:id="@+id/tvPermissionState"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            app:layout_constraintTop_toBottomOf="@+id/ivPermissionState"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:text="권한 허용 안 됨!!"
            android:textColor="@color/black"
            android:textSize="30dp"/>

        <Button
            android:id="@+id/btnRequestPermission"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            app:layout_constraintTop_toBottomOf="@+id/tvPermissionState"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:text="권한 요청"/>

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

 

@AndroidEntryPoint
@SuppressLint("NewApi")
class PermissionRequestActivity :
    BaseActivity<ActivityPermissionRequestBinding>(R.layout.activity_permission_request) {

    private val permissionRequestViewModel: PermissionRequestViewModel by viewModels()

    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
    private val permissions = arrayOf(
        Manifest.permission.READ_MEDIA_IMAGES,
        Manifest.permission.READ_MEDIA_VIDEO,
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        checkCurrentPermissionState()
        setupPermissionStateObserver()
        setupPermissionRequestButton()
    }

    private fun checkCurrentPermissionState() {
        val isPermissionGranted = permissions.all {
            ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
        }
        permissionRequestViewModel.updatePermissionState(isPermissionGranted)
    }

    private fun setupPermissionStateObserver() = lifecycleScope.launch {
        permissionRequestViewModel.permissionState.collect { isAllowed ->
            if (isAllowed) {
                updateUIForGrantedPermission()
            } else {
                updateUIForDeniedPermission()
            }
        }
    }

    private fun updateUIForGrantedPermission() {
        binding.ivPermissionState.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.allowed))
        binding.tvPermissionState.text = "권한 허용됨!!"
    }

    private fun updateUIForDeniedPermission() {
        binding.ivPermissionState.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.not_allowed))
        binding.tvPermissionState.text = "권한 허용 안 됨!!"
    }

    private fun setupPermissionRequestButton() = bind {
        btnRequestPermission.setOnClickListener {
            if (isPermissionGranted(this@PermissionRequestActivity)) {
                permissionRequestViewModel.updatePermissionState(true)
            } else {
                requestPermissions(this@PermissionRequestActivity, 100)
            }
        }
    }

    private fun isPermissionGranted(activity: Activity) = permissions.all {
        ContextCompat.checkSelfPermission(activity, it) == PackageManager.PERMISSION_GRANTED
    }

    private fun requestPermissions(activity: Activity, requestCode: Int) =
        ActivityCompat.requestPermissions(activity, permissions, requestCode)

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            100 -> {
                binding.tvPermissionState.text = "권한 허용됨!!"
                permissionRequestViewModel.updatePermissionState(true)
            }
        }
    }

}

 

마찬가지로 액티비티에도 hilt 관련 어노테이션을 붙이고, 권한을 요청하는 로직들을 작성했다.

이렇게 작성하고 실행하면 아래와 같이 작동한다.

 

 

 

앱을 실행하면 검은 배경의 흰 체크가 있는 아이콘과 "권한 허용 안 됨!!"이라는 글자가 보인다.

권한 요청 버튼을 누르면 권한 요청 팝업이 표시되면서, 허용을 누르면 흰 배경에 검은 체크가 있는 아이콘으로 바뀌고 글자도 "권한 허용됨!!"으로 바뀐다.

앱을 종료하고 재실행하더라도 앞서 권한을 허용했기 때문에 당연히 권한 허용 상태를 뜻하는 아이콘, 글자가 표시된다. 앱 데이터를 모두 지우고 재실행하면 다시 권한 허용 이전 상태로 되돌아가고, 다시 권한을 허용하면 위 플로우가 반복된다.

 

이렇게 간단한 권한 체크 예제를 만들어 봤다. 만약 hilt를 모른다면 hilt 어노테이션인 @AndroidEntryPoint, @HiltViewModel을 지우고 ViewModelProvider로 뷰모델 만들어 쓰면 되고, StateFlow는 LiveData로 바꿔 쓰면 된다. checkSelfPermission()으로 권한들이 허용 상태인지 파악한 다음 requestPermissions()로 요청하는 흐름만 기억하면 된다.

반응형
Comments