Permission Handler es un componente de aplicaciones Android responsable de verificar, solicitar y procesar los resultados de permisos en tiempo de ejecución. Según la Android Developer Guide, 2024, un controlador de permisos centraliza la lógica de checkSelfPermission, requestPermissions y shouldShowRequestPermissionRationale en una sola clase o ViewModel. Esto simplifica el mantenimiento del código y mejora las pruebas.
Puntos clave
Permission Handler es un patrón arquitectónico para la gestión centralizada de permisos en tiempo de ejecución de Android. En lugar de llamadas dispersas a ContextCompat.checkSelfPermission y ActivityCompat.requestPermissions por todo el código de la aplicación, toda la lógica de solicitudes y procesamiento de resultados se concentra en una sola clase. Esto reduce la duplicación, simplifica el mantenimiento y hace que el código sea más predecible.
La necesidad de Permission Handler surgió con la introducción de permisos en tiempo de ejecución en Android 6.0. Antes de eso, todos los permisos se solicitaban al instalar la aplicación, y el código podía usar cualquier API sin verificaciones. Después de migrar al modelo de ejecución, cada uso de un permiso peligroso requiere una verificación de tres pasos: checkSelfPermission, requestPermissions, onRequestPermissionsResult. Distribuir esta lógica entre Activity y Fragment genera duplicación en línea y errores. Según Google I/O 2019, centralizar el manejo de permisos reduce la cantidad de errores relacionados con Permission Denial en un promedio del 60 por ciento.
Un buen Permission Handler proporciona una interfaz limpia para el código que lo invoca. La Activity o Fragment no deben conocer los detalles de la solicitud — llaman a un método como requestCamera(callback), y el propio handler gestiona la verificación del estado, la visualización de la justificación, la invocación del diálogo del sistema y la transmisión del resultado al callback. Esto implementa el principio de responsabilidad única y separa la lógica de negocio del código de permisos de la plataforma.
Un Permission Handler se vuelve necesario cuando una aplicación utiliza 3 o más permisos peligrosos. Para aplicaciones simples con un solo permiso (por ejemplo, cámara para un escáner de códigos QR), una llamada directa puede ser suficiente. Pero para una aplicación móvil típica con cámara, geolocalización, notificaciones y almacenamiento — un handler centralizado es esencial para el mantenimiento.
Un Permission Handler típico consta de tres capas: un contrato de interfaz, una implementación con ActivityResultLauncher y una capa de ViewModel. La interfaz define métodos de solicitud para cada permiso — requestCamera, requestLocation, requestStorage. La implementación vincula estos métodos con los contratos ActivityResultContracts.RequestPermission correspondientes.
Componentes clave de la arquitectura:
Esta arquitectura permite intercambiar fácilmente las implementaciones en las pruebas: en lugar de un ActivityResultLauncher real, se utiliza un mock que devuelve un resultado predefinido sin interacción con el sistema. Esto es fundamental para las pruebas unitarias de la lógica de UI, donde iniciar una Activity para un diálogo de permisos es imposible.
El Permission Handler debe tener en cuenta el ciclo de vida de Activity y Fragment. Los launchers se registran en el ActivityResultRegistry, que guarda y restaura automáticamente el estado al rotar la pantalla o al recrear la Activity. El handler no debe almacenar referencias directas a Activity o Fragment — en su lugar, use WeakReference o pase el registry a través del constructor. Esto evita pérdidas de memoria y fallos durante los cambios de configuración.
Una implementación básica de Permission Handler se construye sobre ActivityResultContracts.RequestPermission. El handler recibe el ActivityResultRegistry de ComponentActivity o Fragment y registra launchers para cada permiso. Cada launcher acepta una lambda de callback que se invoca después de que el usuario responda.
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
}
}
El handler se inicializa en el onCreate de la Activity a través de registerForActivityResult, que proporciona acceso al ActivityResultRegistry. Después de la inicialización, el handler está listo para procesar solicitudes durante todo el ciclo de vida de la Activity. Es importante llamar a initialize antes de la primera solicitud, de lo contrario el launcher no se registrará.
Integrar un Permission Handler con ViewModel es el enfoque más avanzado. El ViewModel gestiona el estado de las solicitudes, mientras que el Handler solo realiza llamadas de plataforma. El ViewModel contiene StateFlow<PermissionUiState>, donde UiState describe qué permiso se está solicitando y qué resultado se recibió. La Activity se suscribe a este StateFlow y delega la solicitud al 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()
}
En este modelo, la Activity verifica isPermissionGranted a través del Handler al inicio, mientras que el ViewModel solo gestiona el estado. Si el permiso no está concedido — la Activity se suscribe a uiState, llama a requestCamera del Handler y pasa el resultado de vuelta al ViewModel a través de onPermissionResult. Separar el código de plataforma de la lógica de negocio permite probar el ViewModel sin dependencias de Android.
Las pruebas unitarias de Permission Handler son posibles gracias a la interfaz PermissionHandler. En las pruebas, se crea un FakePermissionHandler que simula varios escenarios: permiso concedido, denegado, Never Ask Again. Cada escenario se prueba de forma independiente. Esto es especialmente importante para probar la lógica de UI que debe reaccionar correctamente a los tres resultados.
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
}
}
La implementación fake permite probar ViewModel sin un emulador. Simplemente establezca cameraResult en el valor deseado y verifique que el ViewModel actualice correctamente su UiState. Las pruebas de integración verifican el PermissionHandler real con ActivityScenario, pero normalmente solo hay 2-3 de estas pruebas por aplicación — los escenarios restantes se cubren con pruebas unitarias usando fakes.
Los errores típicos al trabajar con Permission Handler incluyen: no verificar checkSelfPermission antes de cada llamada a la API, ignorar shouldShowRequestPermissionRationale, volver a llamar a requestPermissions después de Never Ask Again y almacenar launchers sin considerar el ciclo de vida de la Activity. Examinemos cada problema y su solución.
El error más común es llamar a una API sin verificar el estado del permiso. Los desarrolladores asumen que si un permiso se concedió una vez, permanecerá para siempre. Sin embargo, el usuario puede revocarlo a través de la configuración en cualquier momento. Un Permission Handler siempre debe llamar a isPermissionGranted antes de realizar una operación sensible. El segundo error popular es ignorar shouldShowRequestPermissionRationale y repetir la solicitud, lo que lleva a una denegación instantánea sin diálogo en modo Never Ask Again.
Las mejores prácticas incluyen: crear una única instancia de Handler para todo el ciclo de vida de la Activity, usar SharedFlow para pasar resultados al ViewModel, registrar todas las solicitudes y denegaciones para análisis, y mostrar un diálogo de justificación personalizado antes del del sistema en la primera denegación. Seguir estas reglas garantiza un manejo estable de permisos en todas las versiones de Android.
Preguntas frecuentes
Permission Handler es un componente para la gestión centralizada de permisos en tiempo de ejecución, que encapsula checkSelfPermission, requestPermissions y shouldShowRequestPermissionRationale. Simplifica el mantenimiento del código y mejora las pruebas.
Se recomienda usar ActivityResultContracts.RequestPermission de la biblioteca androidx.activity. Reemplaza el obsoleto onRequestPermissionsResult y proporciona una API de callback limpia con un resultado Boolean.
Para un solo permiso, un Handler no es obligatorio — puede usar una llamada directa al launcher RequestPermission en la Activity. Un Handler se vuelve necesario con 3 o más permisos para evitar la duplicación de código.
Cree una interfaz PermissionHandler y su implementación fake para pruebas unitarias. El fake devuelve resultados predefinidos sin llamadas al sistema. Esto permite probar la ViewModel y la lógica de UI sin emulador.
Después de la denegación, verifique shouldShowRequestPermissionRationale. Si el método devuelve false — el modo Never Ask Again está activo. El Handler debe devolver PermissionResult.DENIED(false), y la UI debe mostrar un botón para ir a Configuración.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también