Entérate de todos los anuncios de Firebase Summit y descubre cómo Firebase puede ayudarte a acelerar el desarrollo de las apps y a ejecutarlas con confianza. Más información

Recibir mensajes en una aplicación de Flutter

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 diversos estados en los que puede estar 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 generalmente ocurre cuando el usuario ha presionado el botón "Inicio" en el dispositivo, ha cambiado a otra aplicación usando el conmutador 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 útiles de mensajes a través de FCM:

  • La aplicación debe haberse abierto al menos una vez (para permitir el registro con FCM).
  • En iOS, si el usuario desliza la aplicación desde el conmutador de aplicaciones, debe volver a abrirse manualmente para que los mensajes de fondo 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 abrirse manualmente para que los mensajes comiencen a funcionar.
  • En la web, debe haber solicitado un token (usando getToken() ) con su certificado web push.

Solicitar permiso para recibir mensajes (Apple y Web)

En iOS, macOS y web, antes de que se puedan recibir cargas útiles de FCM en su dispositivo, primero debe pedir permiso al usuario.

El paquete firebase_messaging proporciona una API simple para solicitar permisos a través del método requestPermission . Esta API acepta una serie de argumentos con nombre que definen el tipo de permisos que desea solicitar, 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 sensibles. 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 de 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 usar para determinar la decisión general del usuario:

  • authorized : El usuario concedió el permiso.
  • denied : el usuario denegó el permiso.
  • notDetermined : el usuario aún no ha elegido si conceder el permiso.
  • provisional : El usuario concedió permiso provisional

Las otras propiedades en NotificationSettings devuelven si un permiso específico está habilitado, deshabilitado o no es compatible con el dispositivo actual.

Una vez que se ha otorgado el permiso y se han entendido los diferentes tipos de estado del dispositivo, su aplicación ahora puede comenzar a manejar las cargas útiles de 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 información variada sobre la carga útil, como de dónde proviene, la identificación única, la hora de envío, si contenía una notificación y más. Dado que el mensaje se recuperó mientras su aplicación está en primer plano, puede acceder directamente al estado y contexto de su aplicación Flutter.

Mensajes en 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, debe crear un canal de notificación de "Prioridad alta".
  • En iOS, puede 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 los 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 los mensajes incluso cuando su aplicación no se está ejecutando.

Hay algunas cosas a tener en cuenta acerca de su controlador de mensajes de fondo:

  1. No debe ser una función anónima.
  2. Debe ser una función de nivel superior (por ejemplo, no un método de clase que requiera inicialización).
  3. Debe anotarse con @pragma('vm:entry-point') justo encima de la declaración de la función (de lo contrario, puede eliminarse durante la agitación del árbol para el modo de liberación).
@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 en su propio aislamiento 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 IU. Sin embargo, puede realizar lógica como solicitudes HTTP, realizar operaciones de E/S (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 detener automáticamente el proceso.

Web

En la web, escriba un trabajador de servicio de JavaScript que se ejecute en segundo plano. Use el trabajador de servicio para manejar los mensajes de fondo.

Para comenzar, cree un nuevo archivo en su directorio web y 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 de messaging .

A continuación, el trabajador debe estar registrado. Dentro del archivo de entrada, después de que se haya cargado el archivo main.dart.js , registre 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>

A continuación, reinicie su aplicación Flutter. El trabajador se registrará y cualquier mensaje de fondo se manejará a través de este archivo.

Interacción de manejo

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 termina la aplicación, se iniciará; si está en segundo plano, pasará a primer plano.

Según el 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á un Future que contiene un RemoteMessage . Una vez consumido, el RemoteMessage se eliminará.
  • onMessageOpenedApp : una Stream que publica un RemoteMessage cuando la aplicación se abre desde un estado de fondo.

Se recomienda que se manejen 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 usando un StatefulWidget.

Localizar mensajes

Puede enviar cadenas localizadas de dos maneras diferentes:

  • Guarda 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 local nativa del sistema operativo

He aquí cómo usar el segundo método:

Androide

  1. 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>
    
  2. Especifique los mensajes traducidos en el directorio values- language . Por ejemplo, especifique mensajes en francés en resources/values-fr/strings.xml :

    <string name="notification_title">Bonjour le monde</string>
    <string name="notification_message">C'est un message</string>
    
  3. En la carga útil del servidor, en lugar de usar title , message y body keys, use title_loc_key y body_loc_key para su mensaje localizado y configúrelos en el atributo de name 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

  1. Especifique sus mensajes de idioma predeterminado en Base.lproj/Localizable.strings :

    "NOTIFICATION_TITLE" = "Hello World";
    "NOTIFICATION_MESSAGE" = "This is a message";
    
  2. Especifique los mensajes traducidos en el directorio de language .lproj . Por ejemplo, especifique mensajes en francés en fr.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 un análisis más detallado. BigQuery le permite analizar los datos con BigQuery SQL, exportarlos a otro proveedor de la nube o usar los datos para sus modelos de aprendizaje automático personalizados. Una exportación a BigQuery incluye todos los datos disponibles para los mensajes, independientemente del tipo de mensaje o si el mensaje se envía a través de la API o el redactor de notificaciones.

Para habilitar la exportación, primero siga los pasos descritos aquí , luego siga estas instrucciones:

Androide

Puede usar el siguiente código: dart 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 usar la versión v9 del SDK. La versión v9 debe empaquetarse, por lo que debe usar un paquete como esbuild por ejemplo, para que el trabajador de servicio funcione. Vea la aplicación de ejemplo para ver cómo lograr esto.

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 olvides ejecutar yarn build para exportar la nueva versión de tu service worker a la carpeta web .