Permission Handler è un componente delle applicazioni Android responsabile della verifica, richiesta e elaborazione dei risultati delle autorizzazioni runtime. Secondo la Android Developer Guide, 2024, un gestore di autorizzazioni centralizza la logica di checkSelfPermission, requestPermissions e shouldShowRequestPermissionRationale in una singola classe o ViewModel. Questo semplifica la manutenzione del codice e migliora i test.
Punti chiave
Permission Handler è un pattern architetturale per la gestione centralizzata delle autorizzazioni runtime Android. Invece di chiamate sparse di ContextCompat.checkSelfPermission e ActivityCompat.requestPermissions in tutto il codice dell'applicazione, tutta la logica di richiesta e elaborazione dei risultati è concentrata in una singola classe. Questo riduce la duplicazione, semplifica la manutenzione e rende il codice più prevedibile.
La necessità di Permission Handler è nata con l'introduzione delle autorizzazioni runtime in Android 6.0. Prima di ciò, tutte le autorizzazioni venivano richieste durante l'installazione e il codice dell'applicazione poteva utilizzare qualsiasi API senza verifiche. Dopo il passaggio al modello runtime, ogni utilizzo di un'autorizzazione pericolosa richiede una verifica in tre fasi: checkSelfPermission, requestPermissions, onRequestPermissionsResult. Distribuire questa logica tra Activity e Fragment porta a duplicazione inline ed errori. Secondo Google I/O 2019, centralizzare la gestione delle autorizzazioni riduce il numero di bug relativi a Permission Denial in media del 60 percento.
Un buon Permission Handler fornisce un'interfaccia pulita per il codice chiamante. L'Activity o il Fragment non devono conoscere i dettagli della richiesta — chiamano un metodo come requestCamera(callback), e l'handler stesso gestisce la verifica dello stato, la visualizzazione della motivazione, l'invocazione del dialogo di sistema e il passaggio del risultato al callback. Questo implementa il principio di responsabilità singola e separa la logica di business dal codice delle autorizzazioni della piattaforma.
Un Permission Handler diventa necessario quando un'applicazione utilizza 3 o più autorizzazioni pericolose. Per applicazioni semplici con una sola autorizzazione (ad esempio, fotocamera per uno scanner di codici QR), una chiamata diretta può essere sufficiente. Ma per una tipica applicazione mobile con fotocamera, geolocalizzazione, notifiche e archiviazione — un handler centralizzato è essenziale per la manutenibilità.
Un Permission Handler tipico è composto da tre livelli: un contratto di interfaccia, un'implementazione con ActivityResultLauncher e un livello ViewModel. L'interfaccia definisce i metodi di richiesta per ciascuna autorizzazione — requestCamera, requestLocation, requestStorage. L'implementazione lega questi metodi ai corrispondenti contratti ActivityResultContracts.RequestPermission.
Componenti chiave dell'architettura:
Questa architettura consente di scambiare facilmente le implementazioni nei test: invece di un ActivityResultLauncher reale, si utilizza un mock che restituisce un risultato predefinito senza interazione con il sistema. Questo è fondamentale per i test unitari della logica dell'interfaccia utente, dove avviare un'Activity per un dialogo di autorizzazione è impossibile.
Il Permission Handler deve tenere conto del ciclo di vita di Activity e Fragment. I launcher vengono registrati nell'ActivityResultRegistry, che salva e ripristina automaticamente lo stato durante la rotazione dello schermo e la ricreazione dell'Activity. L'handler non deve memorizzare riferimenti diretti ad Activity o Fragment — invece, utilizzare WeakReference o passare il registry attraverso il costruttore. Questo previene perdite di memoria e crash durante i cambi di configurazione.
Un'implementazione di base di Permission Handler è costruita su ActivityResultContracts.RequestPermission. L'handler riceve l'ActivityResultRegistry da ComponentActivity o Fragment e registra i launcher per ciascuna autorizzazione. Ogni launcher accetta un callback lambda che viene invocato dopo la risposta dell'utente.
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
}
}
L'handler viene inizializzato nell'onCreate dell'Activity tramite registerForActivityResult, che fornisce l'accesso all'ActivityResultRegistry. Dopo l'inizializzazione, l'handler è pronto per elaborare le richieste durante l'intero ciclo di vita dell'Activity. È importante chiamare initialize prima della prima richiesta, altrimenti il launcher non verrà registrato.
Integrare un Permission Handler con ViewModel è l'approccio più avanzato. Il ViewModel gestisce lo stato delle richieste, mentre l'Handler esegue solo chiamate di piattaforma. Il ViewModel contiene StateFlow<PermissionUiState>, dove UiState descrive quale autorizzazione viene richiesta e quale risultato è stato ricevuto. L'Activity si sottoscrive a questo StateFlow e delega la richiesta all'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()
}
In questo modello, l'Activity verifica isPermissionGranted tramite l'Handler all'avvio, mentre il ViewModel gestisce solo lo stato. Se l'autorizzazione non è concessa — l'Activity si sottoscrive a uiState, chiama requestCamera dall'Handler e passa il risultato al ViewModel tramite onPermissionResult. Separare il codice piattaforma dalla logica di business consente di testare il ViewModel senza dipendenze Android.
I test unitari di Permission Handler sono possibili grazie all'interfaccia PermissionHandler. Nei test, viene creato un FakePermissionHandler che simula vari scenari: autorizzazione concessa, negata, Never Ask Again. Ogni scenario viene testato indipendentemente. Questo è particolarmente importante per testare la logica dell'interfaccia utente che deve reagire correttamente a tutti e tre i risultati.
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
}
}
L'implementazione fake consente di testare il ViewModel senza un emulatore. Basta impostare cameraResult al valore desiderato e verificare che il ViewModel aggiorni correttamente il suo UiState. I test di integrazione verificano il PermissionHandler reale con ActivityScenario, ma di solito ci sono solo 2-3 di questi test per applicazione — gli scenari rimanenti sono coperti da test unitari con fake.
Gli errori tipici quando si lavora con Permission Handler includono: non verificare checkSelfPermission prima di ogni chiamata API, ignorare shouldShowRequestPermissionRationale, richiamare requestPermissions dopo Never Ask Again e memorizzare i launcher senza considerare il ciclo di vita dell'Activity. Esaminiamo ogni problema e la sua soluzione.
L'errore più comune è chiamare un'API senza verificare lo stato dell'autorizzazione. Gli sviluppatori presumono che se un'autorizzazione è stata concessa una volta, rimarrà per sempre. Tuttavia, l'utente può revocarla tramite le impostazioni in qualsiasi momento. Un Permission Handler deve sempre chiamare isPermissionGranted prima di eseguire un'operazione sensibile. Il secondo errore comune è ignorare shouldShowRequestPermissionRationale e ripetere la richiesta, che porta a un rifiuto immediato senza dialogo in modalità Never Ask Again.
Le migliori pratiche includono: creare una singola istanza di Handler per l'intero ciclo di vita dell'Activity, utilizzare SharedFlow per passare i risultati al ViewModel, registrare tutte le richieste e i rifiuti per analisi e mostrare un dialogo di motivazione personalizzato prima di quello di sistema al primo rifiuto. Seguire queste regole garantisce una gestione stabile delle autorizzazioni su tutte le versioni di Android.
Domande frequenti
Permission Handler è un componente per la gestione centralizzata delle autorizzazioni runtime, che incapsula checkSelfPermission, requestPermissions e shouldShowRequestPermissionRationale. Semplifica la manutenzione del codice e migliora i test.
Si consiglia di utilizzare ActivityResultContracts.RequestPermission dalla libreria androidx.activity. Sostituisce il deprecato onRequestPermissionsResult e fornisce un'API callback pulita con un risultato Boolean.
Per una singola autorizzazione, un Handler non è obbligatorio — è possibile utilizzare una chiamata diretta al launcher RequestPermission nell'Activity. Un Handler diventa necessario con 3 o più autorizzazioni per evitare la duplicazione del codice.
Creare un'interfaccia PermissionHandler e la sua implementazione fake per i test unitari. Il fake restituisce risultati predefiniti senza chiamate di sistema. Questo consente di testare ViewModel e logica dell'interfaccia utente senza emulatore.
Dopo il rifiuto, verificare shouldShowRequestPermissionRationale. Se il metodo restituisce false — la modalità Never Ask Again è attiva. L'Handler deve restituire PermissionResult.DENIED(false) e l'interfaccia utente deve mostrare un pulsante per andare alle Impostazioni.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche