Permission Handler је компонента Android апликације одговорна за проверу, захтевање и обраду резултата runtime дозвола. Према Android Developer Guide, 2024, руковалац дозволама централизује логику checkSelfPermission, requestPermissions и shouldShowRequestPermissionRationale у једној класи или ViewModel-у. Ово поједностављује одржавање кода и побољшава тестирање.
Главно
Permission Handler — је архитектонски образац за централизовано управљање runtime дозволама Android-а. Уместо разбацаних позива ContextCompat.checkSelfPermission и ActivityCompat.requestPermissions широм кода апликације, цела логика захтева и обраде резултата концентрише се у једној класи. Ово смањује дуплирање, поједностављује одржавање и чини код предвидљивијим.
Потреба за Permission Handler-ом настала је увођењем runtime дозвола у Android 6.0. Пре тога, све дозволе су се захтевале приликом инсталације, а код апликације је могао да користи било које API без провера. Након преласка на runtime модел, свако коришћење опасне дозволе захтева тростепену проверу: checkSelfPermission, requestPermissions, onRequestPermissionsResult. Размазивање ове логике по Activity-ју и Fragment-има доводи до дуплирања и грешака. Према подацима Google I/O 2019, централизација обраде дозвола смањује број грешака везаних за Permission Denial у просеку за 60 процената.
Добар Permission Handler пружа чист интерфејс за позивајући код. Activity или Fragment не морају да знају детаље захтева — они позивају метод типа requestCamera(callback), а handler сам управља провером статуса, приказивањем rationale, позивањем системског дијалога и прослеђивањем резултата у callback. Ово имплементира принцип јединствене одговорности и одваја пословну логику од платформског кода дозвола.
Permission Handler постаје неопходан када апликација користи 3 или више опасних дозвола. За једноставне апликације са једном дозволом (нпр. камера за скенер QR кодова) може се користити директан позив. Али за типичну мобилну апликацију са камером, геолокацијом, обавештењима и складиштем — централизовани handler је обавезан за одржавање.
Типичан Permission Handler се састоји од три нивоа: интерфејс-уговор, имплементација са ActivityResultLauncher-ом и слој за ViewModel. Интерфејс дефинише методе захтева за сваку дозволу — requestCamera, requestLocation, requestStorage. Имплементација повезује ове методе са одговарајућим уговорима ActivityResultContracts.RequestPermission.
Кључне компоненте архитектуре:
Оваква архитектура омогућава лаку замену имплементације у тестовима: уместо стварног ActivityResultLauncher-а користи се mock који враћа предефинисани резултат без интеракције са системом. Ово је критично за јединично тестирање UI логике, где покретање Activity-ја за дијалог дозвола није могуће.
Permission Handler мора да узима у обзир животни циклус Activity-ја и Fragment-а. Launcher-и се региструју у ActivityResultRegistry, који аутоматски чува и враћа стање при ротацији екрана и поновном креирању Activity-ја. Handler не треба да чува директне референце на Activity или Fragment — уместо тога користите WeakReference или проследите registry кроз конструктор. Ово спречава цурење меморије и падове при променама конфигурације.
Основна имплементација Permission Handler-а се гради на ActivityResultContracts.RequestPermission. Handler добија ActivityResultRegistry из ComponentActivity или Fragment-а и региструје launcher-е за сваку дозволу. Сваки launcher прима ламбда-повратни позив који се позива након одговора корисника.
sealed class PermissionResult {
object GRANTED : PermissionResult()
data class DENIED(
val shouldShowRationale: Boolean
) : PermissionResult()
}
interface PermissionHandler {
fun requestCamera(
callback: (PermissionResult) -> Unit
)
fun requestLocation(
callback: (PermissionResult) -> Unit
)
fun isPermissionGranted(
permission: String
): Boolean
}
class AndroidPermissionHandler(
private val registry: ActivityResultRegistry,
private val context: Context
) : PermissionHandler {
private var cameraLauncher: ActivityResultLauncher<String>? = null
fun initialize() {
cameraLauncher = registry.register(
"camera_permission",
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
pendingCameraCallback?.invoke(
PermissionResult.GRANTED
)
} else {
val rationale = ActivityCompat.shouldShowRequestPermissionRationale(
context as Activity,
Manifest.permission.CAMERA
)
pendingCameraCallback?.invoke(
PermissionResult.DENIED(rationale)
)
}
}
}
private var pendingCameraCallback:
((PermissionResult) -> Unit)? = null
override fun requestCamera(
callback: (PermissionResult) -> Unit
) {
if (isPermissionGranted(
Manifest.permission.CAMERA
)) {
callback.invoke(PermissionResult.GRANTED)
return
}
pendingCameraCallback = callback
cameraLauncher?.launch(
Manifest.permission.CAMERA
)
}
override fun isPermissionGranted(
permission: String
): Boolean {
return ContextCompat.checkSelfPermission(
context, permission
) == PackageManager.PERMISSION_GRANTED
}
}
Handler се иницијализује у onCreate Activity-ја кроз registerForActivityResult, који пружа приступ ActivityResultRegistry-ју. Након иницијализације, handler је спреман да обрађује захтеве током целог животног циклуса Activity-ја. Важно је позвати initialize пре првог захтева, иначе launcher неће бити регистрован.
Интеграција Permission Handler-а са ViewModel-ом је најнапреднији приступ. ViewModel управља стањем захтева, а Handler извршава само платформске позиве. ViewModel садржи StateFlow<PermissionUiState>, где UiState описује која се дозвола захтева и који је резултат добијен. Activity се претплаћује на овај StateFlow и делегира захтев Handler-у.
class PermissionsViewModel : ViewModel() {
private val _uiState =
MutableStateFlow<PermissionUiState>(
PermissionUiState.Idle
)
val uiState: StateFlow<PermissionUiState> = _uiState.asStateFlow()
fun onCameraRequested() {
_uiState.value = PermissionUiState.RequestingCamera
}
fun onPermissionResult(
permission: String,
result: PermissionResult
) {
when (result) {
PermissionResult.GRANTED -> {
_uiState.value = PermissionUiState.Granted(permission)
}
is PermissionResult.DENIED -> {
_uiState.value = PermissionUiState.Denied(
permission,
result.shouldShowRationale
)
}
}
}
}
sealed class PermissionUiState {
object Idle : PermissionUiState()
object RequestingCamera : PermissionUiState()
data class Granted(val permission: String) : PermissionUiState()
data class Denied(
val permission: String,
val shouldShowRationale: Boolean
) : PermissionUiState()
}
У овом моделу Activity проверава isPermissionGranted кроз Handler при покретању, а ViewModel управља само стањем. Ако дозвола није одобрена — Activity се претплаћује на uiState, позива requestCamera из Handler-а и прослеђује резултат назад ViewModel-у кроз onPermissionResult. Раздвајање платформског кода и пословне логике омогућава тестирање ViewModel-а без Android зависности.
Јединично тестирање Permission Handler-а је могуће захваљујући интерфејсу PermissionHandler. У тестовима се креира FakePermissionHandler који имитира различите сценарије: дозвола одобрена, одбијена, Never Ask Again. Сваки сценариј се тестира независно. Ово је посебно важно за тестирање UI логике која мора правилно да реагује на сва три исхода.
class FakePermissionHandler : PermissionHandler {
var cameraResult: PermissionResult =
PermissionResult.GRANTED
var grantedPermissions: Set<String> =
setOf(Manifest.permission.CAMERA)
override fun requestCamera(
callback: (PermissionResult) -> Unit
) {
callback.invoke(cameraResult)
}
override fun isPermissionGranted(
permission: String
): Boolean {
return permission in grantedPermissions
}
}
Fake имплементација омогућава тестирање ViewModel-а без емулатора. Довољно је подесити cameraResult на жељену вредност и проверити да ViewModel правилно ажурира UiState. Интеграциони тестови проверавају стварни PermissionHandler са ActivityScenario, али обично постоји 2-3 таква теста за целу апликацију — остали сценарији су покривени јединичним тестовима са fake-овима.
Типичне грешке при раду са Permission Handler-ом укључују: одсуство провере checkSelfPermission пре сваког API позива, игнорисање shouldShowRequestPermissionRationale, поновно позивање requestPermissions при Never Ask Again и чување launcher-а без узимања у обзир животног циклуса Activity-ја. Размотримо сваки проблем и решење.
Најчешћа грешка — позив API-ја без провере статуса дозволе. Програмери претпостављају да ако је дозвола једном одобрена, остаће заувек. Међутим, корисник може да је опозове кроз подешавања у било ком тренутку. Permission Handler мора увек да позива isPermissionGranted пре извршења осетљиве операције. Друга популарна грешка — игнорисање shouldShowRequestPermissionRationale и поновни захтев који доводи до тренутног одбијања без дијалога при Never Ask Again.
Најбоље праксе укључују: креирање једне инстанце Handler-а за цео животни циклус Activity-ја, коришћење SharedFlow за пренос резултата у ViewModel, евидентирање свих захтева и одбијања за аналитику, као и приказивање прилагођеног rationale дијалога пре системског при првом одбијању. Праћење ових правила гарантује стабилан рад са дозволама на свим верзијама Android-а.
Често постављана питања
Permission Handler — компонента за централизовано управљање runtime дозволама, која инкапсулира checkSelfPermission, requestPermissions и shouldShowRequestPermissionRationale. Поједностављује одржавање кода и побољшава тестирање.
Препоручује се ActivityResultContracts.RequestPermission из библиотеке androidx.activity. Он замењује застарели onRequestPermissionsResult и пружа чист callback API са Boolean резултатом.
За једну дозволу Handler није обавезан — можете користити директан позив RequestPermission launcher-а у Activity-ју. Handler постаје неопходан код 3 или више дозвола да би се избегло дуплирање кода.
Направите интерфејс PermissionHandler и његову fake имплементацију за јединичне тестове. Fake враћа предефинисане резултате без системских позива. Ово омогућава тестирање ViewModel-а и UI логике без емулатора.
Након одбијања проверите shouldShowRequestPermissionRationale. Ако је метод вратио false — активиран је режим Never Ask Again. Handler треба да врати PermissionResult.DENIED(false), а UI да прикаже дугме за прелазак у Подешавања.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође