Qué es una notificación en Android y qué componentes intervienen
Una notificación es un mensaje que el sistema muestra fuera de la interfaz principal de tu app (barra de estado, panel de notificaciones, pantalla de bloqueo). Su objetivo es informar o permitir una acción rápida. En Android intervienen varios componentes del sistema:
- NotificationManager: servicio del sistema que publica, actualiza y elimina notificaciones.
- NotificationChannel (Android 8.0+): “canal” obligatorio donde se agrupan notificaciones con el mismo comportamiento (sonido, importancia, vibración).
- NotificationCompat: API de compatibilidad (AndroidX) para construir notificaciones que funcionen en múltiples versiones.
- PendingIntent: “intención diferida” que el sistema ejecuta en nombre de tu app cuando el usuario toca la notificación o una acción.
Canales de notificación (Android 8.0+)
Desde Android 8.0 (API 26), toda notificación debe pertenecer a un canal. El canal se crea una sola vez (normalmente al iniciar la app) y luego se reutiliza. El usuario puede modificar desde Ajustes el comportamiento del canal (silenciarlo, cambiar importancia, etc.).
Importancia y comportamiento
- IMPORTANCE_HIGH: puede mostrar heads-up (banner) si hay sonido/vibración y el sistema lo permite.
- IMPORTANCE_DEFAULT: se muestra normalmente, sin heads-up en muchos casos.
- IMPORTANCE_LOW/MIN: más silenciosa, menos intrusiva.
Una vez creado, cambiar propiedades del canal desde código no siempre surte efecto si el usuario ya lo personalizó; en general, se recomienda mantener estables los canales y crear nuevos si necesitas un comportamiento distinto.
Construcción de una notificación con NotificationCompat
Para compatibilidad, usa NotificationCompat.Builder (AndroidX). Elementos típicos:
- Small icon obligatorio:
setSmallIcon(...). - Título y texto:
setContentTitle,setContentText. - Prioridad (pre-API 26):
setPriority(...)influye en versiones antiguas; en API 26+ manda la importancia del canal. - Auto-cancel:
setAutoCancel(true)para que se cierre al tocarla. - Acciones: botones con
addAction(...).
PendingIntent: concepto, tipos y compatibilidad
PendingIntent permite que el sistema ejecute una operación más tarde con los permisos/identidad de tu app. Se usa para:
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
- Abrir una Activity al tocar la notificación:
PendingIntent.getActivity(...). - Enviar un Broadcast (por ejemplo, acción “Marcar como leído”):
PendingIntent.getBroadcast(...). - Iniciar un Service (casos específicos):
PendingIntent.getService(...).
Flags importantes (Android 12+ y seguridad)
Desde Android 12 (API 31) debes declarar si el PendingIntent es mutable o inmutable:
- FLAG_IMMUTABLE: recomendado por defecto; el sistema/otras apps no pueden modificar el intent interno.
- FLAG_MUTABLE: úsalo solo si necesitas que el sistema lo modifique (por ejemplo, algunos casos con RemoteInput o APIs específicas).
Además:
- FLAG_UPDATE_CURRENT: si ya existe un PendingIntent equivalente, actualiza sus extras.
- requestCode: permite diferenciar PendingIntents; útil si publicas varias notificaciones con destinos distintos.
Práctica paso a paso: disparar una notificación y navegar a una pantalla específica
Objetivo: al pulsar un botón en la app, se publica una notificación en un canal. Al tocarla, se abre una pantalla de detalle (Activity) con un ID recibido por extras. También añadiremos una acción secundaria (botón) que abre la misma pantalla.
Paso 1: dependencias necesarias
Asegúrate de tener AndroidX Core (normalmente ya viene). Si necesitas declararlo explícitamente:
dependencies { implementation("androidx.core:core-ktx:1.13.1")}Paso 2: permiso de notificaciones (Android 13+)
En Android 13 (API 33) se requiere el permiso en tiempo de ejecución para mostrar notificaciones. Decláralo en el manifest:
<manifest ...> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> ...</manifest>Luego, solicita el permiso en runtime cuando corresponda (por ejemplo, antes de publicar la primera notificación). Si ya tienes un flujo de permisos en tu app, integra esta solicitud allí. Para esta práctica, lo importante es: si no se concede, la notificación no aparecerá en Android 13+.
Paso 3: crear el canal de notificación
Crea un helper para registrar el canal una sola vez. Puedes llamarlo desde tu Application o desde la primera pantalla que lo necesite.
object NotificationChannels { const val CHANNEL_GENERAL_ID = "general" fun create(context: android.content.Context) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { val name = "General" val descriptionText = "Notificaciones generales de la app" val importance = android.app.NotificationManager.IMPORTANCE_DEFAULT val channel = android.app.NotificationChannel( CHANNEL_GENERAL_ID, name, importance ).apply { description = descriptionText } val manager = context.getSystemService(android.app.NotificationManager::class.java) manager.createNotificationChannel(channel) } }}Nota: el nombre y descripción son visibles para el usuario en Ajustes del sistema. No uses datos sensibles.
Paso 4: crear la pantalla destino (Activity) que se abrirá al tocar la notificación
Crea una Activity (por ejemplo, NotificationDetailActivity) que lea un extra y muestre el contenido. Ejemplo mínimo:
class NotificationDetailActivity : androidx.activity.ComponentActivity() { override fun onCreate(savedInstanceState: android.os.Bundle?) { super.onCreate(savedInstanceState) val itemId = intent.getStringExtra(EXTRA_ITEM_ID) ?: "(sin id)" setContent { androidx.compose.material3.Text(text = "Detalle desde notificación: $itemId") } } companion object { const val EXTRA_ITEM_ID = "extra_item_id" }}Declárala en el manifest si no se registra automáticamente:
<application ...> <activity android:name=".NotificationDetailActivity" /></application>Paso 5: construir el PendingIntent para abrir la pantalla específica
Al tocar la notificación queremos abrir NotificationDetailActivity con un ID. Usaremos FLAG_IMMUTABLE y FLAG_UPDATE_CURRENT para actualizar extras si reutilizamos el mismo requestCode.
private fun detailPendingIntent( context: android.content.Context, itemId: String): android.app.PendingIntent { val intent = android.content.Intent(context, NotificationDetailActivity::class.java).apply { putExtra(NotificationDetailActivity.EXTRA_ITEM_ID, itemId) flags = android.content.Intent.FLAG_ACTIVITY_NEW_TASK or android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP } val flags = android.app.PendingIntent.FLAG_UPDATE_CURRENT or (if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { android.app.PendingIntent.FLAG_IMMUTABLE } else 0) return android.app.PendingIntent.getActivity( context, 1001, intent, flags )}Consideración: si necesitas múltiples notificaciones con distintos destinos simultáneos, usa un requestCode distinto por notificación (por ejemplo, derivado del ID) para que no se pisen.
Paso 6: publicar la notificación con acción y auto-cancel
Ahora construimos y publicamos la notificación. Usa un ID numérico para poder actualizarla o cancelarla después.
fun showExampleNotification(context: android.content.Context, itemId: String) { NotificationChannels.create(context) val contentIntent = detailPendingIntent(context, itemId) val actionIntent = detailPendingIntent(context, itemId) val notification = androidx.core.app.NotificationCompat.Builder( context, NotificationChannels.CHANNEL_GENERAL_ID ) .setSmallIcon(android.R.drawable.ic_dialog_info) .setContentTitle("Nuevo evento") .setContentText("Toca para ver el detalle del item $itemId") .setContentIntent(contentIntent) .setAutoCancel(true) .addAction( android.R.drawable.ic_menu_view, "Ver", actionIntent ) .setPriority(androidx.core.app.NotificationCompat.PRIORITY_DEFAULT) .build() androidx.core.app.NotificationManagerCompat.from(context) .notify(2001, notification)}Notas de compatibilidad:
setPriorityafecta principalmente a Android 7.1 y anteriores; en Android 8.0+ se usa la importancia del canal.- El icono debe ser monocromo en muchos estilos modernos; aquí se usa un recurso del sistema por simplicidad, pero en producción usa un icono propio.
Paso 7: disparar la notificación desde la app
En tu pantalla principal, crea un botón que llame a showExampleNotification. Ejemplo con Compose:
@Composable fun NotificationDemoScreen() { val context = androidx.compose.ui.platform.LocalContext.current androidx.compose.material3.Button(onClick = { showExampleNotification(context, itemId = "A-123") }) { androidx.compose.material3.Text("Enviar notificación") }}Paso 8: pruebas en distintas versiones de Android
| Versión | Qué verificar | Resultado esperado |
|---|---|---|
| Android 13+ (API 33+) | Permiso POST_NOTIFICATIONS concedido/denegado | Con permiso: aparece la notificación. Sin permiso: no aparece. |
| Android 8.0+ (API 26+) | Canal creado y configurable en Ajustes | La notificación se publica solo si el canal permite notificar; el usuario puede silenciar el canal. |
| Android 7.1 y anteriores | Prioridad y comportamiento básico | La notificación aparece sin necesidad de canal; la prioridad influye en visibilidad. |
Paso 9: checklist de depuración si no aparece la notificación
- En Android 13+: confirma que el permiso está concedido y que el usuario no bloqueó notificaciones para la app.
- En Android 8.0+: revisa el canal en Ajustes: si está en “Silencioso” o bloqueado, no verás alertas.
- Verifica que
setSmallIconesté definido (sin icono, la notificación puede fallar o no mostrarse). - Comprueba que estás usando el ID de canal correcto en el builder.
Acciones de notificación: abrir pantalla vs ejecutar lógica
Las acciones (addAction) pueden abrir una pantalla (Activity) o ejecutar lógica sin abrir UI (BroadcastReceiver). Para acciones que cambian estado (por ejemplo, “Descartar”, “Marcar como leído”), suele ser mejor un BroadcastReceiver para realizar la operación y luego actualizar/cancelar la notificación.
Ejemplo conceptual de acción con broadcast (sin implementarlo completo):
// PendingIntent.getBroadcast(...), Receiver en el manifest, y en onReceive: cancelar notificationIdBuenas prácticas rápidas
- Crea pocos canales, con nombres claros para el usuario (por ejemplo: “Mensajes”, “Recordatorios”, “Sincronización”).
- Usa
FLAG_IMMUTABLEen PendingIntent siempre que sea posible. - Usa IDs estables si quieres actualizar una notificación existente (mismo
notify(id, ...)). - Si tu app tiene navegación compleja, considera construir un back stack adecuado (por ejemplo, con TaskStackBuilder) para que el botón Atrás se comporte como el usuario espera.