Únete de manera presencial y en línea a Firebase Summit el 18 de octubre de 2022. Descubre cómo Firebase puede ayudarte a acelerar el desarrollo de apps, a lanzar la tuya con confianza y a escalar con facilidad. Regístrate ahora

Recibir mensajes en una aplicación de Flutter

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

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).
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.