Dependiendo del estado de un dispositivo, los mensajes entrantes se manejan de manera diferente. Para comprender estos escenarios y cómo integrar FCM en su propia aplicación, primero es importante establecer los distintos estados en los que puede encontrarse un dispositivo:
Estado | Descripción |
---|---|
Primer plano | Cuando la aplicación está abierta, a la vista y en uso. |
Fondo | Cuando la aplicación está abierta, pero en segundo plano (minimizada). Esto suele ocurrir cuando el usuario presiona el botón "Inicio" en el dispositivo, cambia a otra aplicación usando el selector de aplicaciones o tiene la aplicación abierta en una pestaña diferente (web). |
Terminado | Cuando el dispositivo está bloqueado o la aplicación no se está ejecutando. |
Hay algunas condiciones previas que deben cumplirse antes de que la aplicación pueda recibir cargas de mensajes a través de FCM:
- La solicitud debe haberse abierto al menos una vez (para permitir el registro en FCM).
- En iOS, si el usuario elimina la aplicación del selector de aplicaciones, debe volver a abrirla manualmente para que los mensajes en segundo plano comiencen a funcionar nuevamente.
- En Android, si el usuario fuerza el cierre de la aplicación desde la configuración del dispositivo, debe volver a abrirla manualmente para que los mensajes comiencen a funcionar.
- En la web, debe haber solicitado un token (usando
getToken()
) con su certificado push web.
Solicitar permiso para recibir mensajes
En iOS, macOS, web y Android 13 (o posterior), antes de poder recibir cargas útiles de FCM en su dispositivo, primero debe solicitar permiso al usuario.
El paquete firebase_messaging
proporciona una API sencilla para solicitar permiso mediante el método requestPermission
. Esta API acepta una serie de argumentos con nombre que definen el tipo de permisos que le gustaría solicitar, como por ejemplo si los mensajes que contienen cargas útiles de notificación pueden activar un sonido o leer mensajes a través de Siri. De forma predeterminada, el método solicita permisos predeterminados sensatos. La API de referencia proporciona documentación completa sobre para qué sirve cada permiso.
Para comenzar, llame al método desde su aplicación (en iOS se mostrará un modal nativo, en la web se activará el flujo API nativo del navegador):
FirebaseMessaging messaging = FirebaseMessaging.instance;
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
print('User granted permission: ${settings.authorizationStatus}');
La propiedad authorizationStatus
del objeto NotificationSettings
devuelto por la solicitud se puede utilizar para determinar la decisión general del usuario:
-
authorized
: El usuario otorgó permiso. -
denied
: el usuario negó el permiso. -
notDetermined
: el usuario aún no ha elegido si otorgar permiso. -
provisional
: El usuario otorgó permiso provisional
Las otras propiedades en NotificationSettings
indican si un permiso específico está habilitado, deshabilitado o no es compatible con el dispositivo actual.
Una vez que se haya otorgado el permiso y se hayan comprendido los diferentes tipos de estado del dispositivo, su aplicación ahora puede comenzar a manejar las cargas útiles FCM entrantes.
Manejo de mensajes
Según el estado actual de su aplicación, las cargas útiles entrantes de diferentes tipos de mensajes requieren diferentes implementaciones para manejarlas:
Mensajes en primer plano
Para manejar mensajes mientras su aplicación está en primer plano, escuche la transmisión onMessage
.
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Got a message whilst in the foreground!');
print('Message data: ${message.data}');
if (message.notification != null) {
print('Message also contained a notification: ${message.notification}');
}
});
La transmisión contiene un RemoteMessage
que detalla diversa información sobre la carga útil, como de dónde era, el ID único, la hora de envío, si contenía una notificación y más. Dado que el mensaje se recuperó mientras su aplicación estaba en primer plano, puede acceder directamente al estado y contexto de su aplicación Flutter.
Mensajes de primer plano y de notificación
Los mensajes de notificación que llegan mientras la aplicación está en primer plano no mostrarán una notificación visible de forma predeterminada, tanto en Android como en iOS. Sin embargo, es posible anular este comportamiento:
- En Android, debes crear un canal de notificación de "alta prioridad".
- En iOS, puedes actualizar las opciones de presentación de la aplicación.
Mensajes de fondo
El proceso de manejo de mensajes en segundo plano es diferente en plataformas nativas (Android y Apple) y basadas en web.
Plataformas Apple y Android
Maneje mensajes en segundo plano registrando un controlador onBackgroundMessage
. Cuando se reciben mensajes, se genera un aislamiento (solo Android, iOS/macOS no requiere un aislamiento separado) que le permite manejar mensajes incluso cuando su aplicación no se está ejecutando.
Hay algunas cosas que debes tener en cuenta sobre tu controlador de mensajes en segundo plano:
- No debe ser una función anónima.
- Debe ser una función de nivel superior (por ejemplo, no un método de clase que requiera inicialización).
- Cuando se usa Flutter versión 3.3.0 o superior, el controlador de mensajes debe anotarse con
@pragma('vm:entry-point')
justo encima de la declaración de función (de lo contrario, se puede eliminar durante la agitación del árbol para el modo de lanzamiento).
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// If you're going to use other Firebase services in the background, such as Firestore,
// make sure you call `initializeApp` before using other Firebase services.
await Firebase.initializeApp();
print("Handling a background message: ${message.messageId}");
}
void main() {
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(MyApp());
}
Dado que el controlador se ejecuta de forma aislada fuera del contexto de su aplicación, no es posible actualizar el estado de la aplicación ni ejecutar ninguna lógica que afecte la interfaz de usuario. Sin embargo, puede realizar lógica como solicitudes HTTP, realizar operaciones de IO (por ejemplo, actualizar el almacenamiento local), comunicarse con otros complementos, etc.
También se recomienda completar su lógica lo antes posible. La ejecución de tareas largas e intensivas afecta el rendimiento del dispositivo y puede hacer que el sistema operativo finalice el proceso. Si las tareas se ejecutan durante más de 30 segundos, el dispositivo puede finalizar automáticamente el proceso.
Web
En la Web, escriba un trabajador de servicio JavaScript que se ejecute en segundo plano. Utilice el trabajador de servicio para manejar mensajes en segundo plano.
Para comenzar, cree un nuevo archivo en su directorio web
y llámelo firebase-messaging-sw.js
:
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.js");
firebase.initializeApp({
apiKey: "...",
authDomain: "...",
databaseURL: "...",
projectId: "...",
storageBucket: "...",
messagingSenderId: "...",
appId: "...",
});
const messaging = firebase.messaging();
// Optional:
messaging.onBackgroundMessage((message) => {
console.log("onBackgroundMessage", message);
});
El archivo debe importar tanto la aplicación como los SDK de mensajería, inicializar Firebase y exponer la variable messaging
.
A continuación, se debe dar de alta al trabajador. Dentro del archivo de entrada, después de que se haya cargado el archivo main.dart.js
, registre a su trabajador:
<html>
<body>
...
<script src="main.dart.js" type="application/javascript"></script>
<script>
if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// ADD THIS LINE
navigator.serviceWorker.register('/firebase-messaging-sw.js');
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
// ...
});
}
</script>
Luego reinicie su aplicación Flutter. El trabajador quedará registrado y cualquier mensaje de fondo se manejará a través de este archivo.
Manejo de la interacción
Dado que las notificaciones son una señal visible, es común que los usuarios interactúen con ellas (presionando). El comportamiento predeterminado tanto en Android como en iOS es abrir la aplicación. Si se da por terminada la solicitud se iniciará; si está en segundo plano, pasará a primer plano.
Dependiendo del contenido de una notificación, es posible que desee controlar la interacción del usuario cuando se abre la aplicación. Por ejemplo, si se envía un nuevo mensaje de chat a través de una notificación y el usuario lo presiona, es posible que desee abrir la conversación específica cuando se abra la aplicación.
El paquete firebase-messaging
proporciona dos formas de manejar esta interacción:
-
getInitialMessage()
: si la aplicación se abre desde un estado terminado, se devolverá unFuture
que contiene unRemoteMessage
. Una vez consumido, elRemoteMessage
se eliminará. -
onMessageOpenedApp
: unaStream
que publica unRemoteMessage
cuando la aplicación se abre desde un estado en segundo plano.
Se recomienda manejar ambos escenarios para garantizar una experiencia de usuario fluida para sus usuarios. El siguiente ejemplo de código describe cómo se puede lograr esto:
class Application extends StatefulWidget {
@override
State<StatefulWidget> createState() => _Application();
}
class _Application extends State<Application> {
// It is assumed that all messages contain a data field with the key 'type'
Future<void> setupInteractedMessage() async {
// Get any messages which caused the application to open from
// a terminated state.
RemoteMessage? initialMessage =
await FirebaseMessaging.instance.getInitialMessage();
// If the message also contains a data property with a "type" of "chat",
// navigate to a chat screen
if (initialMessage != null) {
_handleMessage(initialMessage);
}
// Also handle any interaction when the app is in the background via a
// Stream listener
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
}
void _handleMessage(RemoteMessage message) {
if (message.data['type'] == 'chat') {
Navigator.pushNamed(context, '/chat',
arguments: ChatArguments(message),
);
}
}
@override
void initState() {
super.initState();
// Run code required to handle interacted messages in an async function
// as initState() must not be async
setupInteractedMessage();
}
@override
Widget build(BuildContext context) {
return Text("...");
}
}
La forma en que maneja la interacción depende de la configuración de su aplicación. El ejemplo anterior muestra una ilustración básica utilizando un StatefulWidget.
Localizar mensajes
Puede enviar cadenas localizadas de dos maneras diferentes:
- Almacena el idioma preferido de cada uno de tus usuarios en tu servidor y envía notificaciones personalizadas para cada idioma
- Incruste cadenas localizadas en su aplicación y utilice la configuración regional nativa del sistema operativo.
A continuación se explica cómo utilizar el segundo método:
Androide
Especifique sus mensajes en el idioma predeterminado en
resources/values/strings.xml
:<string name="notification_title">Hello world</string> <string name="notification_message">This is a message</string>
Especifique los mensajes traducidos en el directorio
values- language
. Por ejemplo, especifique mensajes en francés enresources/values-fr/strings.xml
:<string name="notification_title">Bonjour le monde</string> <string name="notification_message">C'est un message</string>
En la carga útil del servidor, en lugar de usar las claves
title
,message
ybody
, usetitle_loc_key
ybody_loc_key
para su mensaje localizado y configúrelos en el atributo dename
del mensaje que desea mostrar.La carga útil del mensaje se vería así:
{ "data": { "title_loc_key": "notification_title", "body_loc_key": "notification_message" } }
iOS
Especifique sus mensajes en el idioma predeterminado en
Base.lproj/Localizable.strings
:"NOTIFICATION_TITLE" = "Hello World"; "NOTIFICATION_MESSAGE" = "This is a message";
Especifique los mensajes traducidos en el directorio
language .lproj
. Por ejemplo, especifique mensajes en francés enfr.lproj/Localizable.strings
:"NOTIFICATION_TITLE" = "Bonjour le monde"; "NOTIFICATION_MESSAGE" = "C'est un message";
La carga útil del mensaje se vería así:
{ "data": { "title_loc_key": "NOTIFICATION_TITLE", "body_loc_key": "NOTIFICATION_MESSAGE" } }
Habilitar la exportación de datos de entrega de mensajes
Puede exportar los datos de su mensaje a BigQuery para su posterior análisis. BigQuery le permite analizar los datos usando BigQuery SQL, exportarlos a otro proveedor de nube o usar los datos para sus modelos de ML personalizados. Una exportación a BigQuery incluye todos los datos disponibles para los mensajes, independientemente del tipo de mensaje o de si el mensaje se envía a través de la API o del redactor de notificaciones.
Para habilitar la exportación, primero siga los pasos que se describen aquí y luego siga estas instrucciones:
Androide
Puedes utilizar el siguiente código:
await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);
iOS
Para iOS, debe cambiar AppDelegate.m
con el siguiente contenido.
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
#import <Firebase/Firebase.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[[FIRMessaging extensionHelper] exportDeliveryMetricsToBigQueryWithMessageInfo:userInfo];
}
@end
Web
Para Web, debe cambiar su trabajador de servicio para poder utilizar la versión v9 del SDK. La versión v9 debe estar empaquetada, por lo que debe usar un paquete como esbuild
por ejemplo, para que el trabajador del servicio funcione. Consulte la aplicación de ejemplo para ver cómo lograrlo.
Una vez que haya migrado al SDK v9, puede usar el siguiente código:
import {
experimentalSetDeliveryMetricsExportedToBigQueryEnabled,
getMessaging,
} from 'firebase/messaging/sw';
...
const messaging = getMessaging(app);
experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, true);
No olvide ejecutar yarn build
para exportar la nueva versión de su trabajador de servicio a la carpeta web
.
Mostrar imágenes en notificaciones en iOS
En los dispositivos Apple, para que las notificaciones FCM entrantes muestren imágenes de la carga útil de FCM, debe agregar una extensión de servicio de notificación adicional y configurar su aplicación para usarla.
Si está utilizando la autenticación telefónica de Firebase, debe agregar el pod de autenticación de Firebase a su Podfile.
Paso 1: agregar una extensión del servicio de notificaciones
- En Xcode, haga clic en Archivo > Nuevo > Destino...
- Un modal presentará una lista de posibles objetivos; desplácese hacia abajo o use el filtro para seleccionar Extensión del servicio de notificación . Haga clic en Siguiente .
- Agregue un nombre de producto (use "ImageNotification" para seguir este tutorial), configure el idioma en Objective-C y haga clic en Finalizar .
- Habilite el esquema haciendo clic en Activar .
Paso 2: agregar destino al Podfile
Asegúrese de que su nueva extensión tenga acceso al pod Firebase/Messaging
agregándola en el Podfile:
Desde el Navegador, abra el Podfile: Pods > Podfile
Desplácese hasta el final del archivo y agregue:
target 'ImageNotification' do use_frameworks! pod 'Firebase/Auth' # Add this line if you are using FirebaseAuth phone authentication pod 'Firebase/Messaging' end
Instale o actualice sus pods usando
pod install
desde el directorioios
omacos
.
Paso 3: utiliza la extensión auxiliar
En este punto, todo debería seguir funcionando con normalidad. El último paso es invocar la extensión auxiliar.
Desde el navegador, seleccione su extensión ImageNotification
Abra el archivo
NotificationService.m
.En la parte superior del archivo, importe
FirebaseMessaging.h
justo después deNotificationService.h
como se muestra a continuación.Reemplace el contenido de
NotificationService.m
con:#import "NotificationService.h" #import "FirebaseMessaging.h" #import "FirebaseAuth.h" // Add this line if you are using FirebaseAuth phone authentication #import <UIKit/UIKit.h> // Add this line if you are using FirebaseAuth phone authentication @interface NotificationService () @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver); @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent; @end @implementation NotificationService /* Uncomment this if you are using Firebase Auth - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options { if ([[FIRAuth auth] canHandleURL:url]) { return YES; } return NO; } - (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts { for (UIOpenURLContext *urlContext in URLContexts) { [FIRAuth.auth canHandleURL:urlContext.URL]; } } */ - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler { self.contentHandler = contentHandler; self.bestAttemptContent = [request.content mutableCopy]; // Modify the notification content here... [[FIRMessaging extensionHelper] populateNotificationContent:self.bestAttemptContent withContentHandler:contentHandler]; } - (void)serviceExtensionTimeWillExpire { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. self.contentHandler(self.bestAttemptContent); } @end
Paso 4: agrega la imagen a la carga útil
En su carga útil de notificaciones, ahora puede agregar una imagen. Consulte la documentación de iOS sobre cómo crear una solicitud de envío . Tenga en cuenta que el dispositivo exige un tamaño de imagen máximo de 300 KB.