Saltar al contenido
Todas las entradas

Cuándo pedir el permiso de notificaciones en Android (y cómo recuperarse de un Denegar)

Una guía práctica del permiso en tiempo de ejecución POST_NOTIFICATIONS de Android — cuándo pedirlo, cómo preparar la solicitud, y qué hacer cuando alguien lo deniega.

MFKAPPS 6 min de lectura

Desde Android 13, las notificaciones ya no se conceden por defecto — el usuario tiene que decir que sí explícitamente a POST_NOTIFICATIONS, del mismo modo que diría que sí al acceso a la cámara o a la ubicación. Para una app donde las notificaciones cargan con todo el valor — un recordatorio de hidratación, una alerta de medicación — ese único diálogo del sistema marca la diferencia entre que la app funcione y que la app sea un icono que se abre de vez en cuando. Si aciertas mal el momento, la mayoría de la gente toca «No permitir» por reflejo, antes de haber visto una razón para decir que sí. Si lo aciertas, ese mismo diálogo se convierte en algo rutinario. El permiso en sí no cambia; lo que cambia es la solicitud que lo rodea.

El diálogo solo aparece una vez

POST_NOTIFICATIONS se comporta como cualquier otro permiso en tiempo de ejecución de Android: el sistema muestra su diálogo una vez por instalación. Si el usuario lo deniega, la siguiente llamada a requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) devuelve false en silencio — sin diálogo, sin error, solo una denegación inmediata. Esa es la parte que confunde a la gente durante las pruebas: una segunda solicitud de ActivityResultLauncher parece no hacer nada, cuando en realidad está haciendo exactamente lo que se supone que debe hacer.

val requestPermissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { granted ->
    if (granted) {
        scheduleDailyReminder()
    } else {
        showNotificationsDisabledState()
    }
}

Como solo hay una oportunidad real de mostrar el diálogo del sistema, el momento en que llamas a launch() importa más que cómo lo llamas.

No lo pidas en el primer lanzamiento

El reflejo es solicitar durante el onboarding todos los permisos que la app vaya a necesitar alguna vez, para que el resto de la app pueda asumir que todos están concedidos. Para las notificaciones en particular, esto está cerca del peor momento posible. En el primer lanzamiento el usuario no tiene contexto de por qué una app de recordatorios querría interrumpirlo más adelante — lleva viendo el icono diez segundos. Un diálogo de permisos sin una razón previa se lee como fricción, y la fricción en ese momento exacto recibe un «No permitir» reflejo con más frecuencia que uno meditado.

La solución es pedirlo en el punto en el que el valor del permiso es evidente por sí mismo. En Hydrame, ese es el momento en que alguien termina de fijar un objetivo diario de agua — acaba de decirle a la app cuándo quiere beber más, así que un recordatorio no es una interrupción, es la función que pidió. En OldSchool, es justo después de añadir el primer medicamento y su horario. La solicitud sigue a una acción que implica que el usuario ya quiere lo que el permiso habilita; el diálogo del sistema simplemente lo formaliza.

Una pantalla de preparación es mejor que un diálogo del sistema desnudo

El diálogo de permisos del sistema es breve por diseño — un título, un nombre de app, Permitir y No permitir. No puede explicar por qué, y Android no te da forma de personalizar su texto. Lo que sí controlas es la pantalla que se muestra justo antes: una sola frase de contexto, enmarcada en torno al objetivo del propio usuario en lugar de la necesidad de la app.

"Te recordaré beber agua según el horario que acabas de fijar.
 Android necesita tu permiso para mostrar estos recordatorios."

 [ Activar recordatorios ]   [ Ahora no ]

A esto a veces se le llama pantalla de «pre-permiso» o de «pregunta suave», y su valor no es cosmético — cambia lo que significa el diálogo del sistema cuando aparece un momento después. Sin ella, el diálogo del SO es la primera y única explicación que recibe el usuario. Con ella, el diálogo del SO es solo la confirmación de algo que ya había aceptado. Un «Ahora no» en la pantalla de preparación tampoco cuesta nada: no toca el permiso real en absoluto, así que el único intento del sistema sigue disponible más adelante, una vez que el usuario haya pasado más tiempo en la app y tenga más motivos para confiar en la solicitud.

Una denegación no es definitiva — shouldShowRequestPermissionRationale

Una vez que alguien deniega el diálogo del sistema, ActivityCompat.shouldShowRequestPermissionRationale() te indica si merece la pena mostrar de nuevo tu propia justificación antes de la siguiente solicitud, o si el usuario lo ha denegado de forma permanente (marcó «No volver a preguntar», o ya lo denegó una vez bajo las reglas más recientes y estrictas de Android para volver a preguntar).

fun onReminderCardTapped(activity: Activity) {
    val granted = ContextCompat.checkSelfPermission(
        activity, Manifest.permission.POST_NOTIFICATIONS
    ) == PackageManager.PERMISSION_GRANTED

    when {
        granted -> scheduleDailyReminder()
        ActivityCompat.shouldShowRequestPermissionRationale(
            activity, Manifest.permission.POST_NOTIFICATIONS
        ) -> showRationaleThenRequest()
        else -> openAppNotificationSettings(activity)
    }
}

Cuando devuelve false tras una denegación previa, el sistema no volverá a mostrar su diálogo sin importar cómo llames a launch() — el único camino de vuelta es la página de ajustes de notificaciones de la app, a la que se llega con un Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) acotado a tu paquete. No escondas esto detrás de un mensaje genérico tipo «revisa tus ajustes»; el intent lleva al usuario directamente a la pantalla correcta, sin necesidad de navegar.

Diseña el «no» como un estado real, no como un bug

Algunos usuarios lo van a denegar y lo van a decir en serio — construye la app para que eso sea un estado soportado, no uno roto. Hydrame sigue registrando el agua sin recordatorios; simplemente la racha y el historial no reciben un empujón. OldSchool mantiene la lista de medicamentos dentro de la app totalmente utilizable, y muestra un banner persistente y descartable — «Los recordatorios están desactivados» — en lugar de fingir que la función existe cuando no puede dispararse. Un fallo silencioso, en el que un recordatorio se programó pero nunca aparece porque el permiso nunca se concedió, es peor que una función ausente pero honesta: da la impresión de que la app no es fiable en lugar de que fue una decisión que tomó el usuario.

El único diálogo del sistema merece la preparación

POST_NOTIFICATIONS no es una API difícil — registerForActivityResult y una sola cadena de permiso cubren la mecánica en unas pocas líneas. Lo que realmente determina la tasa de concesión es todo lo que rodea a esas líneas: pedirlo después del momento en que el valor se hizo evidente, preparar el terreno con una frase de contexto en lenguaje sencillo, y tener una respuesta real para la denegación en lugar de asumir que no va a ocurrir. Para cualquier app en la que el recordatorio sea el producto, eso no es un detalle de pulido — es lo que hace que la función funcione, sin más.