Notificaciones de React Native: permisos, canales y el ciclo de vida de FCM
Aprende cómo las permisos, los canales de Android, los tokens FCM y los manejadores de estado en primer plano, segundo plano y de cierre se integran en un sistema de notificaciones de React Native con Notifee.
Fuentes: React Native Notifications & Permissions: A Complete Guide for Android and iOS
Para un usuario, una notificación es una línea de texto como “Su pedido ya fue enviado”. Para la aplicación que la envía, esa línea depende de los permisos en tiempo de ejecución, los canales de notificaciones de Android, los tokens del dispositivo, Firebase Cloud Messaging (FCM), los manejadores en segundo plano y una serie de diferencias entre las plataformas Android e iOS. Un error en cualquiera de estos niveles provoca que las notificaciones nunca lleguen o abran la pantalla incorrecta. Esta guía explica cada uno de estos niveles utilizando Notifee y React Native Firebase para que pueda crear un sistema de notificaciones que funcione de manera predecible en cualquier estado de la aplicación.
Notificaciones locales y notificaciones push
Notificaciones locales
Una notificación local se crea en el dispositivo por la propia aplicación, sin intervención de ningún servidor. Un recordatorio de reunión es un ejemplo típico:
Reminder
Your meeting starts in 10 minutes.
La aplicación puede mostrarlas o programar su envío sin conexión. Notifee ofrece una API de React Native que funciona sobre los sistemas nativos de notificaciones de ambas plataformas.
Notificaciones push
Una notificación push comienza en un servidor. Tu backend transfiere el mensaje a FCM, FCM lo envía al dispositivo y la aplicación de React Native reacciona:
Your Backend
│
▼
Firebase Cloud Messaging
│
▼
Android / iOS Device
│
▼
React Native Application
FCM distingue entre mensajes de notificación (que el sistema puede mostrar) y mensajes de datos (cargas arbitrarias en formato clave-valor para tu código), y puede dirigirse a un dispositivo individual o a un grupo de dispositivos mediante temas.
El permiso forma parte del estado de tu aplicación
Instalar una aplicación ya no implica necesariamente que muestre notificaciones. Desde Android 13, POST_NOTIFICATIONS es un permiso en tiempo de ejecución, y las aplicaciones dirigidas a Android 13 o versiones posteriores deben solicitarlo antes de mostrar cualquier contenido. iOS siempre ha requerido una autorización explícita del usuario. Considere el permiso como un estado de la aplicación que puede cambiar, y no como un paso de configuración único.
Un flujo sensato verifica el estado, explica las ventajas y luego solicita la autorización; cualquiera de las respuestas permite que la aplicación continúe:
Application starts
│
▼
Check notification permission
│
├── Granted ───────► Continue
│
└── Not granted
│
▼
Explain the benefit
│
▼
Request permission
│
┌─────┴─────┐
▼ ▼
Granted Denied
│ │
▼ ▼
Notifications Continue
Instalación de las bibliotecas
Notifee se encarga de mostrar las notificaciones:
npm install @notifee/react-native
React Native Firebase proporciona el módulo principal de la aplicación y el módulo de mensajería para notificaciones push:
npm install @react-native-firebase/app
npm install @react-native-firebase/messaging
En iOS, instale posteriormente las dependencias nativas de CocoaPods:
cd ios
pod install
cd ..
La configuración nativa varía según la versión de React Native y la configuración del proyecto. Además, las notificaciones push en iOS requieren credenciales APNs en Firebase y la funcionalidad de push habilitada en Xcode. Sigue las documentaciones actuales de cada biblioteca.
Mostrando la primera notificación nativa
El objetivo del primer ejemplo es mostrar una notificación con este título y cuerpo:
New Message
You have received a new message.
Comienza importando Notifee junto con el enum de importancia utilizado para los canales de Android:
import notifee, {
AndroidImportance,
} from '@notifee/react-native';
La función a continuación realiza tres acciones en orden: solicita permisos, crea (o reutiliza) un canal de Android llamado default, y muestra la notificación en ese canal. La acción pressAction con id default indica a Android que abra la aplicación al tocar la notificación; sin ella, un toque no produce efecto alguno.
export async function displayNotification() {
// Request permission
await notifee.requestPermission(); // Create Android notification channel
const channelId = await notifee.createChannel({
id: 'default',
name: 'Default Notifications',
importance: AndroidImportance.DEFAULT,
}); // Display notification
await notifee.displayNotification({
title: 'New Message',
body: 'You have received a new message.', android: {
channelId,
pressAction: {
id: 'default',
},
},
});
}
Llamar repetidamente a createChannel es seguro; un canal ya existente simplemente devuelve su id. Los canales son obligatorios en Android 8.0 (API 26) y versiones posteriores. Active la función desde un botón:
<Button
title="Show Notification"
onPress={displayNotification}
/>
Solicitando el permiso de Android 13
El nombre completo del permiso en Android es:
android.permission.POST_NOTIFICATIONS
React Native lo expone como una constante en PermissionsAndroid:
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
También se debe declarar el permiso con uses-permission en AndroidManifest.xml; de lo contrario, la solicitud se rechaza silenciosamente. Comience con las importaciones:
import {
PermissionsAndroid,
Platform,
} from 'react-native';
La función auxiliar devuelve true de inmediato en iOS y en versiones de Android anteriores a 13 (nivel de API 33), donde las notificaciones están permitidas por defecto. De lo contrario, muestra el diálogo del sistema e indica si el resultado fue GRANTED:
export async function requestAndroidNotificationPermission() {
if (Platform.OS !== 'android') {
return true;
} if (Platform.Version < 33) {
return true;
} const result = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
); return result === PermissionsAndroid.RESULTS.GRANTED;
}
Llámelo desde una pantalla:
const granted =
await requestAndroidNotificationPermission();
y toma una decisión según el resultado:
if (granted) {
console.log('Notifications allowed');
} else {
console.log('Notifications denied');
}
Android puede devolver tres resultados significativos:
GRANTED
DENIED
NEVER_ASK_AGAIN
NEVER_ASK_AGAIN merece especial atención. Después de rechazos repetidos, el sistema deja de mostrar el diálogo, y solo la configuración del sistema puede cambiar esa respuesta.
Preguntar en el momento adecuado
Lanzar la solicitud del sistema en la primera pantalla es un error común y costoso. Un usuario nuevo que vea inmediatamente
"Allow MyApp to send notifications?"
no tiene contexto y probablemente rechazará. Explícalo primero, por ejemplo en una pantalla de bienvenida:
Welcome to MyApp
seguido por una tarjeta que enumere para qué sirven las notificaciones, con un botón:
Enable notifications to receive:
✓ New messages
✓ Order updates
✓ Important alerts[Enable Notifications]
Solo al tocar ese botón se activa el diálogo del sistema, de modo que la solicitud responde a una elección del usuario en lugar de interrumpirlo.
Autenticación en iOS con Notifee
En iOS, el usuario debe autorizar la entrega de forma explícita. Notifee informa del resultado a través de AuthorizationStatus:
import notifee, {
AuthorizationStatus,
} from '@notifee/react-native';
La función auxiliar a continuación considera dos estados como éxito. AUTHORIZED representa un permiso total. PROVISIONAL corresponde al modo silencioso de iOS, en el cual las notificaciones se envían directamente al Centro de Notificaciones sin interrumpir al usuario, quien posteriormente puede conservarlas o desactivarlas. Todo lo demás se considera como un rechazo:
export async function requestIOSNotificationPermission() {
const settings = await notifee.requestPermission(); if (
settings.authorizationStatus ===
AuthorizationStatus.AUTHORIZED
) {
console.log('Notifications authorized');
return true;
} if (
settings.authorizationStatus ===
AuthorizationStatus.PROVISIONAL
) {
console.log('Notifications provisionally authorized');
return true;
} console.log('Notifications denied'); return false;
}
La solicitud en iOS puede incluir una variedad de opciones:
Alerts
Sounds
Badges
CarPlay
Announcements
Provisional notifications
Solicite solo lo que utilice; por ejemplo, la autorización provisional omite completamente el mensaje de solicitud.
Una función de permisos para toda la aplicación
En lugar de distribuir las verificaciones en diferentes pantallas, utilice un único módulo de servicio:
src/
├── services/
│ └── notificationService.ts
├── screens/
├── components/
└── App.tsx
El archivo de servicio:
// notificationService.ts
Combina las rutas de ambas plataformas. En Android aplica la verificación de versión y la solicitud en tiempo de ejecución; en iOS utiliza Notifee y acepta tanto el estado autorizado como el provisional:
import {
Platform,
PermissionsAndroid,
} from 'react-native';import notifee, {
AuthorizationStatus,
} from '@notifee/react-native';export async function requestNotificationPermission() {
if (Platform.OS === 'android') {
if (Platform.Version < 33) {
return true;
} const result = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
); return result === PermissionsAndroid.RESULTS.GRANTED;
} const settings = await notifee.requestPermission(); return (
settings.authorizationStatus ===
AuthorizationStatus.AUTHORIZED ||
settings.authorizationStatus ===
AuthorizationStatus.PROVISIONAL
);
}
Luego las pantallas llaman a una función:
const enabled = await requestNotificationPermission();
Y actúan según la respuesta obtenida:
if (enabled) {
console.log('Ready for notifications');
}
Las versiones recientes de Notifee también pueden solicitar POST_NOTIFICATIONS en Android 13 a través de requestPermission(); consulte la documentación actual de Notifee si prefiere una sola llamada para ambas plataformas.
Canales de notificaciones en Android
Android 8.0 introdujo los canales para que los usuarios puedan controlar de forma independiente las categorías de notificaciones. Una aplicación puede exponer varios:
MyApp
│
├── Messages
├── Orders
├── Promotions
└── Security
Cada canal tiene su propia importancia y sonido. Por lo general, los mensajes merecen una alta importancia para que aparezcan de inmediato:
const messageChannel = await notifee.createChannel({
id: 'messages',
name: 'Messages',
importance: AndroidImportance.HIGH,
});
Mientras que las actualizaciones de pedido pueden utilizar el nivel predeterminado:
const orderChannel = await notifee.createChannel({
id: 'orders',
name: 'Orders',
importance: AndroidImportance.DEFAULT,
});
Luego, una notificación indica el canal al que pertenece. La llamada se muestra en dos partes: primero el título y luego el cuerpo:
await notifee.displayNotification({
title: 'New Message',
body: 'You received a new message.',
Y después las opciones de Android que hacen referencia al ID del canal devuelto por createChannel:
android: {
channelId: messageChannel,
},
});
Los canales están orientados al usuario, no son solo una configuración para desarrolladores. Los usuarios pueden silenciar o bloquear un canal en la configuración del sistema, y una vez creado el canal, la aplicación ya no puede cambiar su importancia de forma programática; el usuario tiene ese control. Elija con cuidado, o utilice un nuevo ID de canal más tarde.
Una estructura que funciona bien en producción separa los canales según su finalidad:
notifications
│
├── messages
├── orders
├── payments
├── security
└── marketing
Un canal de marketing separado permite a los usuarios silenciar las promociones sin perder las alertas de seguridad.
Cómo encaja FCM
Para el envío de notificaciones, FCM se sitúa entre tu backend y cada aplicación instalada:
┌─────────────────┐
│ Your Backend │
└────────┬────────┘
│
▼
┌─────────────────┐
│ FCM │
└────────┬────────┘
│
┌───────┴───────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ Android │ │ iOS │
└──────────┘ └──────────┘
Obtención del token del dispositivo
Cada instalación de la aplicación puede recibir un token de registro de FCM, y tu backend necesita ese token para dirigirse a un dispositivo específico. Importa el módulo de mensajería:
import messaging from '@react-native-firebase/messaging';
y solicita el token:
export async function getFCMToken() {
const token = await messaging().getToken(); console.log('FCM Token:', token); return token;
}
En una aplicación real, el token se envía a tu servidor inmediatamente después de ser obtenido:
const token = await messaging().getToken();
await fetch('https://api.example.com/device-token', {
method: 'POST', headers: {
'Content-Type': 'application/json',
}, body: JSON.stringify({
token,
}),
});
Luego, el backend almacena el token en relación con el usuario conectado y el dispositivo:
User
│
├── userId
├── deviceId
└── fcmToken
Mantener los tokens actualizados
Los tokens cambian, por ejemplo después de reinstalar la aplicación o borrar sus datos. Escucha los eventos de actualización:
import messaging from '@react-native-firebase/messaging';
El receptor transmite cada token nuevo y la función devuelve una función de cancelación de suscripción:
export function subscribeToTokenRefresh() {
return messaging().onTokenRefresh(async token => {
console.log('New FCM token:', token); await sendTokenToBackend(token);
});
}
Un ayudante mínimo para subir archivos, mostrado nuevamente en dos partes, realiza una petición POST en formato JSON:
async function sendTokenToBackend(token: string) {
await fetch('https://api.example.com/device-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}, body: JSON.stringify({
token,
}),
});
}
La propia guía de Firebase indica que se deben gestionar los cambios en los tokens y enviar el nuevo valor al servidor de la aplicación cada vez que esto ocurre. En el lado del servidor, también se deben eliminar los tokens que FCM indique como inválidos, para evitar que se acumulen dispositivos obsoletos.
Los mensajes en primer plano no se muestran automáticamente
El caso más confuso es cuando llega una notificación mientras la aplicación está abierta:
User
│
▼
React Native App
│
▼
FCM message
Escuchar mensajes en primer plano:
import messaging from '@react-native-firebase/messaging';
const unsubscribe = messaging().onMessage(
async remoteMessage => {
console.log('Foreground message:', remoteMessage);
},
);
No obstante, recibir un mensaje y mostrar una notificación del sistema son pasos separados. Mientras la aplicación está en primer plano, FCM entrega el mensaje a tu código pero no muestra nada por sí mismo. Para mostrar una notificación, debes pasar su contenido a Notifee:
import notifee from '@notifee/react-native';
El manejador a continuación se asegura de que el canal exista y luego muestra la notificación; en caso de que el mensaje no contenga un bloque notification (por ejemplo, un mensaje con solo datos), recurre al texto por defecto:
messaging().onMessage(async remoteMessage => {
const channelId = await notifee.createChannel({
id: 'messages',
name: 'Messages',
}); await notifee.displayNotification({
title:
remoteMessage.notification?.title ??
'New Message', body:
remoteMessage.notification?.body ??
'You have a new message.', android: {
channelId,
pressAction: {
id: 'default',
},
},
});
});
Usted controla la presentación, incluyendo la posibilidad de omitirla cuando la pantalla correspondiente ya está visible.
Mensajes en segundo plano y de estado de cierre
Cuando la aplicación está en segundo plano, su comportamiento cambia. En Android, los mensajes de notificación de FCM van directamente a la bandeja del sistema sin invocar su función de respuesta en primer plano, y los mensajes que combinan notificación y datos siguen sus propias reglas para el segundo plano. En iOS, los mensajes en segundo plano con solo datos se procesan según las posibilidades del sistema y pueden sufrir retrasos o limitaciones.
React Native Firebase proporciona un manejador dedicado para los mensajes en segundo plano:
import messaging from '@react-native-firebase/messaging';
messaging().setBackgroundMessageHandler(
async remoteMessage => {
console.log(
'Background message:',
remoteMessage,
);
},
);
Un error frecuente es registrar este manejador dentro de un componente React, como en el siguiente fragmento:
function App() {
messaging().setBackgroundMessageHandler(...);
return <Navigation />;
}
Un despertar en segundo plano podría no montar ningún componente, por lo que el manejador nunca existiría. Regístrelo en el punto de entrada:
// index.js
import messaging from '@react-native-firebase/messaging';messaging().setBackgroundMessageHandler(
async remoteMessage => {
console.log(
'Background message:',
remoteMessage,
);
},
);
y solo entonces registre el componente raíz:
AppRegistry.registerComponent(
appName,
() => App,
);
Reaccionar a los toques en las notificaciones
Un toque generalmente debe llevar al usuario a un lugar específico:
Notification
│
▼
User taps
│
▼
Application opens
│
▼
Order Details
Supongamos que el mensaje contiene un ID de pedido en sus datos:
{
"orderId": "12345"
}
Para aplicaciones reanudadas desde el segundo plano, onNotificationOpenedApp se dispara cuando el usuario toca una notificación. La función de callback lee el ID de remoteMessage.data y puede navegar desde allí:
messaging().onNotificationOpenedApp(
remoteMessage => {
const orderId =
remoteMessage.data?.orderId;
if (orderId) {
// Navigate to order details
console.log(
'Open order:',
orderId,
);
}
},
);
Cuando la aplicación se cerró por completo
Si la aplicación se cerró, la secuencia es la siguiente:
App completely closed
│
▼
User receives notification
│
▼
User taps notification
│
▼
App launches
En ese caso, onNotificationOpenedApp no se activa. En su lugar, solicite la notificación que inició la aplicación una sola vez durante el inicio:
const remoteMessage =
await messaging().getInitialNotification();
if (remoteMessage) {
const orderId =
remoteMessage.data?.orderId; console.log(
'App opened from notification:',
orderId,
);
}
Estos dos métodos solo cubren las notificaciones que mostró FCM. Los toques en notificaciones que usted mismo mostró con Notifee, como las de primer plano mencionadas anteriormente, llegan a través de las propias API de eventos de Notifee (onForegroundEvent, onBackgroundEvent y su getInitialNotification). Tratar solo un tipo de notificación es una causa común de errores como “el toque no tiene efecto”.
Cargas útiles diseñadas para la navegación
Una notificación que solo dice
New Order
nada le indica a la aplicación a dónde debe dirigirse el usuario. Incluya datos estructurados junto al texto visible:
{
"notification": {
"title": "New Order",
"body": "Order #12345 has been confirmed"
},
"data": {
"type": "order",
"orderId": "12345"
}
}
Una función de enrutamiento puede entonces mapear el type a una pantalla:
function handleNotification(data: any) {
if (data.type === 'order') {
navigation.navigate(
'OrderDetails',
{
orderId: data.orderId,
},
);
}
}
Dado que los manejadores de notificaciones suelen ejecutarse fuera de los componentes, la navigation en dichos auxiliares suele ser una referencia de navegación creada en la raíz de la aplicación. Para un tratamiento completo sobre el enrutamiento mediante enlaces profundos, consulte enrutamiento de notificaciones push a través de enlaces profundos en React Native.
Detección de cambios en los permisos después de la instalación
Los permisos no son estables con el tiempo. Un usuario puede permitir las notificaciones el lunes:
Monday:
Notifications = ON
y desactivarlas desde la configuración del sistema el miércoles:
Wednesday:
User opens Android SettingsNotifications = OFF
La aplicación debe darse cuenta de ello. Notifee lee la configuración actual sin solicitar permiso:
import notifee, {
AuthorizationStatus,
} from '@notifee/react-native';
export async function checkNotificationPermission() {
const settings =
await notifee.getNotificationSettings(); if (
settings.authorizationStatus ===
AuthorizationStatus.AUTHORIZED
) {
return true;
} return false;
}
Tenga en cuenta que esta verificación devuelve false para la autorización provisional; incluya PROVISIONAL si su aplicación la trata como habilitada. Ejécutela nuevamente cuando la aplicación vuelva al primer plano.
Cuando el usuario dice que no
No repita la solicitud. Muestre en su lugar un estado claro, como un banner:
Notifications are disabled
con una acción que lleve a la configuración:
Enable notifications from Settings
to receive important updates.[Open Settings]
Notifee ofrece APIs que abren directamente la configuración de notificaciones de la aplicación. Los principios detrás de todo esto caben en una sola tarjeta:
Don't force
Don't spam
Explain the value
Give the user control
Centralización de la configuración de notificaciones
En el código de producción, mantenga todo lo relacionado con las notificaciones en unos pocos módulos bien definidos:
src/
│
├── services/
│ ├── notificationService.ts
│ └── firebaseMessaging.ts
│
├── navigation/
│ └── notificationNavigation.ts
│
├── screens/
│
└── App.tsx
El servicio principal:
// notificationService.ts
Expone un inicializador que solicita permiso, se detiene si el usuario lo rechaza y, de lo contrario, crea el canal por defecto:
import notifee, {
AndroidImportance,
AuthorizationStatus,
} from '@notifee/react-native';export async function initializeNotifications() {
const settings =
await notifee.requestPermission(); const authorized =
settings.authorizationStatus ===
AuthorizationStatus.AUTHORIZED ||
settings.authorizationStatus ===
AuthorizationStatus.PROVISIONAL; if (!authorized) {
return false;
} await notifee.createChannel({
id: 'default',
name: 'Default Notifications',
importance: AndroidImportance.DEFAULT,
}); return true;
}
Llamarlo desde un componente es sencillo:
useEffect(() => {
initializeNotifications();
}, []);
Cuidado: en la primera pantalla esto se muestra de inmediato, lo que genera el problema de sincronización mencionado anteriormente. Llámelo desde el botón de consentimiento en su lugar.
El lado backend
Un modelo de datos típico almacena a los usuarios y sus dispositivos en tablas separadas, ya que un mismo usuario puede tener varios teléfonos o tablets:
users
│
├── id
├── name
└── email
user_devices
│
├── id
├── user_id
├── device_id
├── fcm_token
├── platform
├── created_at
└── updated_at
Un registro de registro de dispositivo podría verse así:
{
"userId": 101,
"deviceId": "device-abc",
"fcmToken": "xxxxx",
"platform": "android"
}
Cuando ocurre un evento empresarial, el backend busca los tokens del usuario y los envía a través de FCM:
Order Created
│
▼
Backend
│
▼
Find user's FCM token
│
▼
Firebase Cloud Messaging
│
▼
User's phone
FCM espera que los mensajes se envíen desde un entorno de confianza, como su servidor de aplicaciones o Cloud Functions, nunca desde el cliente, ya que el envío requiere credenciales del servidor.
Cargas útiles escalables
Diseñe cada carga útil de modo que la aplicación entienda el evento, no solo el texto. Una confirmación de pago podría contener:
{
"notification": {
"title": "Payment Successful",
"body": "Your payment of ₹999 was successful."
},
"data": {
"type": "payment",
"paymentId": "PAY12345",
"screen": "PaymentDetails"
}
}
Un único switch basado en data.type gestiona todo tipo de notificaciones:
switch (data.type) {
case 'payment':
navigation.navigate(
'PaymentDetails',
{
paymentId: data.paymentId,
},
);
break;
case 'order':
navigation.navigate(
'OrderDetails',
{
orderId: data.orderId,
},
);
break;
}
Cada nuevo tipo de notificación corresponde a un nuevo caso. Los valores de datos de FCM son cadenas, por lo que los números deben parsearse explícitamente.
La arquitectura completa
Al combinar todas las partes, una configuración en producción se ve así:
┌──────────────────┐
│ Backend API │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Firebase / FCM │
└────────┬─────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Android │ │ iOS │
└──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
Notification APNs / FCM
Channel │
│ │
└────────────┬────────────┘
▼
React Native App
│
▼
Navigation / UI
En iOS, FCM no entrega los mensajes por sí mismo; los transmite a través del servicio de notificaciones push de Apple (APNs), por lo que es necesario configurar las credenciales de APNs.
Errores comunes
Pedir información al iniciar por primera vez
Este patrón en la primera pantalla, sin ninguna explicación, es el error más frecuente:
useEffect(() => {
requestNotificationPermission();
}, []);
Explique primero el valor y solicite permiso en respuesta a una acción del usuario.
Olvidar los canales
Sin un canal, las notificaciones no aparecerán en Android 8.0 y versiones posteriores. Cree uno antes de mostrarlas:
await notifee.createChannel({
id: 'messages',
name: 'Messages',
});
Esperar que aparezcan notificaciones en primer plano
Al escuchar con
messaging().onMessage(...)
no significa que siempre aparecerá una notificación del sistema visible. Para una presentación consistente en primer plano, muéstrela usted mismo con Notifee.
Ignorar los cambios en el token
Un token guardado una vez deja de funcionar silenciosamente. Utilice
messaging().onTokenRefresh(...)
y envíe cada token nuevo al servidor posterior.
Registrar controladores en segundo plano en los componentes
El manejador de fondo debe encontrarse en el punto de entrada, fuera del árbol de React, como se mostró anteriormente.
Ignorar cambios posteriores en los permisos
Los usuarios pueden revocar permisos o bloquear un canal en cualquier momento. Sus verificaciones deben tener en cuenta cada uno de estos estados:
Granted
Denied
Changed externally
Channel blocked
App notifications disabled
Fuerte de datos sensibles
El texto de la notificación es visible en la pantalla de bloqueo y para cualquier persona cerca del dispositivo. En lugar de
Your bank account ending in 1234 received ₹50,000.
enviar algo neutral:
New account activity
Tap to view details.
y mostrar los detalles solo después de que el usuario se haya autenticado dentro de la aplicación.
Lista de verificación para pruebas
El comportamiento varía según la versión del sistema operativo, el estado de la aplicación y el dispositivo, por lo que se deben realizar pruebas de forma sistemática. En Android:
☐ Android 12
☐ Android 13+
☐ Permission granted
☐ Permission denied
☐ Permission disabled from Settings
☐ Channel enabled
☐ Channel disabled
☐ App foreground
☐ App background
☐ App killed
☐ Notification tapped
☐ Multiple notifications
En iOS:
☐ Permission granted
☐ Permission denied
☐ Permission changed from Settings
☐ App foreground
☐ App background
☐ App terminated
☐ Notification tapped
☐ Badge
☐ Sound
☐ Deep linking
Y para la integración FCM de extremo a extremo:
☐ Token generated
☐ Token stored on backend
☐ Token refresh handled
☐ Foreground message received
☐ Background message received
☐ Notification click handled
☐ Initial notification handled
Flujo en tiempo de ejecución a simple vista
La secuencia completa, desde el inicio de la aplicación hasta la entrega y la navegación, es la siguiente:
APP START
│
▼
Check permissions
│
┌────────┴────────┐
│ │
Allowed Denied
│ │
▼ ▼
Initialize FCM Show explanation
│ │
▼ ▼
Get FCM token Request permission
│
▼
Send token to API
│
▼
App Running
│
┌──────┴──────┐
│ │
▼ ▼
Foreground Background
│ │
▼ ▼
onMessage FCM/APNs
│ │
▼ ▼
Notifee System UI
│ │
└──────┬──────┘
▼
User taps
│
▼
Read notification data
│
▼
Navigation
Notificaciones locales frente a push, y su uso conjunto
Estos dos tipos de notificaciones difieren en varios aspectos predecibles:
- Desencadenante: las notificaciones locales provienen de la aplicación o del dispositivo; las notificaciones push provienen del servidor posterior.
- Red: las notificaciones locales suelen funcionar sin conexión; las push requieren conectividad.
- FCM: no es necesario para las notificaciones locales; generalmente se requiere para las push.
- Recordatorios programados: son adecuados para las notificaciones locales; no es el uso típico de las push.
- Eventos del servidor, actualizaciones de pedidos, mensajes de chat: las push son la opción habitual; las locales solo pueden manejarlos de manera limitada.
- Bibliotecas: Notifee para notificaciones locales; FCM junto con una biblioteca de visualización para las push.
También se combinan bien. Una configuración común permite que FCM entregue el mensaje, tu código lo procese y Notifee lo muestre:
FCM receives message
↓
React Native processes message
↓
Notifee displays native notification
Qué deben ver los usuarios en cada paso
Para la verificación manual, debes saber qué esperar. En Android 13+, aparece un diálogo del sistema:
Allow MyApp to send you notifications?
En la configuración de notificaciones del sistema de la aplicación, los canales que creaste aparecen como interruptores separados:
MyApp
├── Messages
├── Orders
└── Promotions
En iOS, aparece el mensaje de autorización del sistema, y las notificaciones entregadas se muestran en el Centro de Notificaciones. Para las notificaciones push, la consola de Firebase puede enviar un mensaje de prueba directamente a un token del dispositivo, que es la forma más rápida de separar los errores del backend de los del cliente:
Firebase Console
↓
Cloud Messaging
↓
Test Message
↓
FCM Token
Finalmente, al tocar una notificación de pedido:
Notification
↓
Order #12345
debe llevar al pantalla correspondiente:
Order Details Screen
Mejores prácticas en producción
Programa la solicitud de permisos de manera que los usuarios comprendan por qué las notificaciones son útiles, en lugar de solicitarlas simplemente porque la aplicación se inició. Mantén la lógica centralizada en un único servicio en lugar de duplicarla en diferentes pantallas:
notificationService.ts
Separa los tipos de notificaciones en canales o categorías, como por ejemplo:
messages
orders
payments
security
marketing
Diseña los datos a enviar para la navegación, incluyendo campos apropiados según sea necesario:
type
id
screen
Más allá de eso:
- Vincula los tokens a usuarios y dispositivos autenticados, almacénalos de forma segura y gestiona su actualización.
- Mantén la información sensible fuera del texto de las notificaciones, ya que este es visible fuera de la aplicación.
- Prueba en dispositivos reales, ya que los emuladores y simuladores pueden comportarse de manera diferente.
Conclusión
Un sistema de notificaciones funcional es mucho más que una sola llamada como
showNotification();
Se trata de la combinación de todos estos aspectos:
Permissions
+
Notification Channels
+
FCM Tokens
+
Foreground Handling
+
Background Handling
+
Notification Clicks
+
Deep Linking
+
Backend Integration
La clave es una clara división de responsabilidades. FCM se encarga de entregar los mensajes. El sistema operativo gestiona los permisos y la presentación nativa. Notifee permite a React Native controlar la presentación local. Tu backend decide cuándo enviar qué y a quién. Visto como un único sistema, se presenta de esta manera:
┌─────────────────┐
│ Backend API │
└────────┬────────┘
│
▼
┌─────────────────┐
│ FCM │
└────────┬────────┘
│
┌────────┴────────┐
▼ ▼
┌───────────┐ ┌───────────┐
│ Android │ │ iOS │
│ Channel │ │ APNs │
└─────┬─────┘ └─────┬─────┘
│ │
└────────┬────────┘
▼
React Native
│
▼
Notifee
│
▼
Native UI
Con esas fronteras establecidas, las notificaciones se comportan de manera predecible en Android e iOS, así como en estados en primer plano, de fondo y terminados.
Referencia rápida
Solicitar el permiso en Android:
await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
);
Solicitar el permiso en iOS:
await notifee.requestPermission();
Crear un canal en Android:
await notifee.createChannel({
id: 'messages',
name: 'Messages',
});
Mostrar una notificación:
await notifee.displayNotification({
title: 'New Message',
body: 'You have a new message.',
android: {
channelId: 'messages',
},
});
Escuchar mensajes FCM en primer plano:
messaging().onMessage(async message => {
console.log(message);
});
Escuchar cambios en el token:
messaging().onTokenRefresh(token => {
console.log(token);
});
Gestionar la apertura de notificaciones desde segundo plano:
messaging().onNotificationOpenedApp(message => {
console.log(message.data);
});
Lección relacionada
- Conectar un modo Turbo dentro de la aplicación de extremo a extremo con React Native Codegen — Definir una especificación tipada, ejecutar codegen e implementar un módulo Turbo en iOS y Android con métodos síncronos, de Promise, de callback y de emisor de eventos.
- Enrutamiento de notificaciones push a través de enlaces profundos en React Native — Configurar enlaces universales y enlaces de aplicación, integrar Pusher Beams en una aplicación React Native y mapear URLs web a pantallas nativas para que un toque en una notificación abra el lugar correcto.
- Enviar notificaciones push a través de enlaces profundos en React Native — Configura enlaces universales y enlaces de aplicación, integra Pusher Beams en una app React Native, y asocia URLs web con pantallas nativas para que al tocar una notificación se abra el lugar correcto.
- Conexión end to end de un módulo Turbo dentro de la aplicación con React Native Codegen — Define una especificación tipada, ejecuta codegen e implementa un módulo Turbo en iOS y Android con métodos síncronos, Promise, callback y emisor de eventos.
- Dentro de un servicio de notificaciones multi-proveedor: colas, soluciones alternativas y confianza — Un recorrido por el diseño de un servicio de notificaciones fiable: almacenamiento de credenciales, solución alternativa de proveedores, reintentos con BullMQ y listas de espera para mensajes defectuosos, equidad entre usuarios y verificación de firmas de webhook.
- Envío y recepción de SMS en Node.js con Twilio Webhooks y Express — Crea un pequeño servidor Express que envíe textos a través de Twilio, reciba respuestas mediante un webhook expuesto con ngrok y las responda automáticamente con TwiML.