Permission Handler este o componentă a aplicației Android responsabilă pentru verificarea, solicitarea și procesarea rezultatelor permisiunilor runtime. Potrivit Android Developer Guide, 2024, handler-ul de permisiuni centralizează logica checkSelfPermission, requestPermissions și shouldShowRequestPermissionRationale într-o singură clasă sau ViewModel. Acest lucru simplifică întreținerea codului și îmbunătățește testarea.
Principalele puncte
Permission Handler — este un model arhitectural pentru gestionarea centralizată a permisiunilor runtime Android. În locul apelurilor dispersate ContextCompat.checkSelfPermission și ActivityCompat.requestPermissions în întregul cod al aplicației, întreaga logică de solicitare și procesare a rezultatelor este concentrată într-o singură clasă. Aceasta reduce duplicarea, simplifică întreținerea și face codul mai previzibil.
Necesitatea Permission Handler a apărut odată cu introducerea permisiunilor runtime în Android 6.0. Înainte de aceasta, toate permisiunile erau solicitate la instalare, iar codul aplicației putea utiliza orice API fără verificări. După trecerea la modelul runtime, fiecare utilizare a unei permisiuni periculoase necesită o verificare în trei pași: checkSelfPermission, requestPermissions, onRequestPermissionsResult. Dispersarea acestei logici între Activity și Fragment duce la duplicare inline și erori. Conform datelor Google I/O 2019, centralizarea procesării permisiunilor reduce numărul de bug-uri legate de Permission Denial cu 60 la sută în medie.
Un Permission Handler bun oferă o interfață curată pentru codul apelant. Activity sau Fragment nu trebuie să cunoască detaliile solicitării — ele apelează o metodă de tip requestCamera(callback), iar handler-ul gestionează singur verificarea stării, afișarea rationale, apelarea dialogului de sistem și transmiterea rezultatului către callback. Aceasta implementează principiul responsabilității unice și separă logica de afaceri de codul de platformă al permisiunilor.
Permission Handler devine necesar atunci când aplicația utilizează 3 sau mai multe permisiuni periculoase. Pentru aplicații simple cu o singură permisiune (de exemplu, camera pentru scaner de coduri QR) se poate recurge la apel direct. Dar pentru o aplicație mobilă tipică cu cameră, geolocalizare, notificări și stocare — handler-ul centralizat este obligatoriu pentru întreținere.
Un Permission Handler tipic este format din trei niveluri: interfață-contract, implementare cu ActivityResultLauncher și strat pentru ViewModel. Interfața definește metodele de solicitare pentru fiecare permisiune — requestCamera, requestLocation, requestStorage. Implementarea leagă aceste metode de contractele corespunzătoare ActivityResultContracts.RequestPermission.
Componentele cheie ale arhitecturii:
Această arhitectură permite înlocuirea ușoară a implementării în teste: în locul ActivityResultLauncher real se utilizează un mock care returnează un rezultat predefinit fără interacțiune cu sistemul. Acest lucru este critic pentru testarea unitară a logicii UI, unde lansarea Activity pentru dialogul de permisiuni este imposibilă.
Permission Handler trebuie să țină cont de ciclul de viață al Activity și Fragment. Launcher-ele sunt înregistrate în ActivityResultRegistry, care salvează și restaurează automat starea la rotirea ecranului și recrearea Activity. Handler nu trebuie să păstreze referințe directe către Activity sau Fragment — în schimb, utilizați WeakReference sau transmiteți registry prin constructor. Aceasta previne scurgerile de memorie și crash-urile la modificările de configurare.
Implementarea de bază a Permission Handler se construiește pe ActivityResultContracts.RequestPermission. Handler primește ActivityResultRegistry de la ComponentActivity sau Fragment și înregistrează launcher-e pentru fiecare permisiune. Fiecare launcher acceptă o lambda-callback care este apelată după răspunsul utilizatorului.
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-ul este inițializat în onCreate al Activity prin registerForActivityResult, care oferă acces la ActivityResultRegistry. După inițializare, handler-ul este gata să proceseze solicitări pe tot parcursul ciclului de viață al Activity. Este important să apelați initialize înainte de prima solicitare, altfel launcher-ul nu va fi înregistrat.
Integrarea Permission Handler cu ViewModel este cea mai avansată abordare. ViewModel gestionează starea solicitărilor, iar Handler execută doar apelurile de platformă. ViewModel conține StateFlow<PermissionUiState>, unde UiState descrie ce permisiune este solicitată și ce rezultat a fost obținut. Activity se abonează la acest StateFlow și deleagă solicitarea către 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()
}
În acest model, Activity verifică isPermissionGranted prin Handler la pornire, iar ViewModel gestionează doar starea. Dacă permisiunea nu a fost acordată — Activity se abonează la uiState, apelează requestCamera din Handler și transmite rezultatul înapoi către ViewModel prin onPermissionResult. Separarea codului de platformă și a logicii de afaceri permite testarea ViewModel fără dependențe Android.
Testarea unitară a Permission Handler este posibilă datorită interfeței PermissionHandler. În teste se creează un FakePermissionHandler care simulează diverse scenarii: permisiune acordată, respinsă, Never Ask Again. Fiecare scenariu este testat independent. Acest lucru este deosebit de important pentru testarea logicii UI care trebuie să reacționeze corect la toate cele trei rezultate.
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
}
}
Implementarea Fake permite testarea ViewModel fără emulator. Este suficient să setați cameraResult la valoarea dorită și să verificați dacă ViewModel actualizează corect UiState. Testele de integrare verifică PermissionHandler real cu ActivityScenario, dar de obicei există 2-3 astfel de teste pentru întreaga aplicație — restul scenariilor sunt acoperite de teste unitare cu fake-uri.
Erori tipice la lucrul cu Permission Handler includ: lipsa verificării checkSelfPermission înainte de fiecare apel API, ignorarea shouldShowRequestPermissionRationale, re-apelarea requestPermissions în caz de Never Ask Again și păstrarea launcher-elor fără a ține cont de ciclul de viață al Activity. Să analizăm fiecare problemă și soluția sa.
Cea mai frecventă eroare — apelarea API fără verificarea stării permisiunii. Dezvoltatorii presupun că dacă o permisiune a fost acordată odată, va rămâne pentru totdeauna. Însă utilizatorul o poate revoca din setări în orice moment. Permission Handler trebuie să apeleze întotdeauna isPermissionGranted înainte de a executa o operațiune sensibilă. A doua eroare populară — ignorarea shouldShowRequestPermissionRationale și re-solicitarea care duce la refuzul imediat fără dialog în cazul Never Ask Again.
Cele mai bune practici includ: crearea unei singure instanțe Handler pentru întregul ciclu de viață al Activity, utilizarea SharedFlow pentru transmiterea rezultatelor către ViewModel, logarea tuturor solicitărilor și refuzurilor pentru analitică, precum și afișarea unui dialog personalizat rationale înainte de cel sistemic la prima respingere. Respectarea acestor reguli garantează o funcționare stabilă cu permisiunile pe toate versiunile de Android.
Întrebări frecvente
Permission Handler — este o componentă pentru gestionarea centralizată a permisiunilor runtime, încapsulând checkSelfPermission, requestPermissions și shouldShowRequestPermissionRationale. Simplifică întreținerea codului și îmbunătățește testarea.
Se recomandă ActivityResultContracts.RequestPermission din biblioteca androidx.activity. Acesta înlocuiește onRequestPermissionsResult învechit și oferă un API callback curat cu rezultat Boolean.
Pentru o singură permisiune Handler nu este obligatoriu — puteți utiliza apelul direct al launcher-ului RequestPermission în Activity. Handler devine necesar la 3 sau mai multe permisiuni pentru a evita duplicarea codului.
Creați o interfață PermissionHandler și implementarea sa fake pentru teste unitare. Fake returnează rezultate predefinite fără apeluri de sistem. Acest lucru permite testarea ViewModel și a logicii UI fără emulator.
După refuz, verificați shouldShowRequestPermissionRationale. Dacă metoda a returnat false — modul Never Ask Again este activat. Handler trebuie să returneze PermissionResult.DENIED(false), iar UI să afișeze un buton de navigare către Setări.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și