Notificaciones Push en Android: Firebase Cloud Messaging (4)

Curso Programación Android

Este artículo forma parte del Curso de Programación Android que tienes disponible de forma completamente gratuita en sgoliver.net

En los artículos anteriores ya hemos resuelto dos de los problemas planteados en el primer capítulo. Por un lado hemos aprendido a identificar usuarios individuales para poder dirigir mensajes a dispositivos concretos. Por otro, ya tenemos preparada nuestra aplicación para recibir notificaciones tanto cuando no se encuentra visible como cuando es la aplicación en primer plano. En esta cuarta entrega de la serie vamos a centrarnos en el envío y recepción de datos adicionales dentro de los mensajes de Firebase Cloud Messaging.

Hasta ahora, he estado utilizando los términos “notificación” y “mensaje” casi indistintamente, pero para ser exactos debería haber utilizado siempre el concepto de “mensaje de notificación” o “mensaje con carga de notificación” (en inglés, “notification payload“).

Firebase Cloud Messaging distingue entre dos tipos de mensajes:

  1. Mensajes de notificación, o con carga de notificación.
  2. Mensajes de datos, o con carga de datos.

Los mensajes de notificación, que son los que hemos estudiado hasta ahora, son los más limitados, aunque suficientes en multitud de ocasiones. Pueden contener hasta 2 Kb de información, pero distribuida en un conjunto de claves o campos predeterminados. Estos campos predeterminados son los que ya nos deben sonar de artículos anteriores: título, texto, prioridad, sonido, … (existen más campos que los que aparecen en la sección de Notificaciones de la consola de Firebase, más adelante veremos algunos de ellos). El utilizar únicamente campos predefinidos permite al sistema gestionar automáticamente los mensajes en determinadas circunstancias, por ejemplo generando las notificaciones por nosotros cuando la aplicación se encuentra en segundo plano.

Por su parte, los mensajes de datos permiten enviar conjuntos de clave-valor totalmente personalizados hasta un límite de 4 Kb de información. No tendremos que limitarnos a los campos disponibles, sino que podremos definir los nuestros propios según las necesidades de nuestra aplicación. Sin embargo, al no basarse en campos predefinidos, la gestión completa de estos mensajes dependerá exclusivamente de nuestra aplicación, es decir, el sistema no podrá hacer nada por nosotros como en el caso de las notificaciones.

En cualquier caso, es importante entender que estos dos tipos de mensajes no son excluyentes, es decir, se pueden enviar mensajes que contengan ambos tipos de carga, por un lado la información asociada a la notificación y por otro los datos personalizados. En breve veremos cómo y dónde podremos gestionar los mensajes en cada caso.

Como hemos indicado, los mensajes de datos “puros” (es decir, sin carga de notificación) deben ser gestionados en su totalidad por nuestra aplicación. Sin embargo, el lugar donde podremos gestionar estos datos ya lo conocemos, será el mismo servicio de recepción de mensajes (extendido de FirebaseMessagingService) que ya utilizamos en el artículo anterior. En el caso de mensajes de datos puros éste será el único lugar donde se podrá gestionar la información recibida, tanto si la aplicación está visible como en segundo plano.

Dentro del método onMessageReceived() de este servicio podremos acceder a los datos incluidos en el mensaje de forma análoga a como accedíamos a los datos de la carga de notificación, con la salvedad de que usaremos el método getData() en vez de getNotification().

Imaginemos que nuestros mensajes de datos van a incluir por ejemplo dos campos (claves) personalizados para informar del estado de conexión de un usuario. Llamaremos a estos campos “usuario” y “estado”. Dentro del método onMessageReceived() podríamos obtener los valores de estas claves obteniendo los datos del mensaje con getData() y llamando posteriormente al método get() pasando como parámetro el nombre de cada clave:

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {

    if (remoteMessage.getNotification() != null) {
        String titulo = remoteMessage.getNotification().getTitle();
        String texto = remoteMessage.getNotification().getBody();

        Log.d(LOGTAG, "NOTIFICACION RECIBIDA");
        Log.d(LOGTAG, "Título: " + titulo);
        Log.d(LOGTAG, "Texto: " + texto);

        //Opcional: mostramos la notificación en la barra de estado
        showNotification(titulo, texto);
    }

    if(remoteMessage.getData() != null) {
        Log.d(LOGTAG, "DATOS RECIBIDOS");
        Log.d(LOGTAG, "Usuario: " + remoteMessage.getData().get("usuario"));
        Log.d(LOGTAG, "Estado: " + remoteMessage.getData().get("estado"));
    }
}

Como podéis ver hemos dejado también la parte de gestión de notificaciones que ya implementamos en el artículo pasado porque como hemos indicado es posible recibir mensajes que contengan ambos tipos de información (notificación + datos personalizados).

Para probar si todo funciona correctamente vamos a utilizar una vez más la consola de Firebase, pero nos encontraremos con un pequeño problema. Si nos fijamos bien, el campo “Texto del mensaje” es obligatorio en el formulario de envío de mensajes. Este campo es propio de los mensajes de notificación por lo que acabamos de descubrir que la consola de Firebase no soporta el envío de mensajes de datos puros, sino tan sólo de mensajes de notificación o de mensajes “mixtos”, es decir, con carga simultánea de notificación y de datos. Este problema viene a agravar la cuarta de las limitaciones que encontramos en el primer artículo de la serie sobre Firebase Cloud Messaging, es decir, la Consola de Firebase puede resultar muy práctica para el envío de mensajes en multitud de ocasiones, pero puede no adaptarse a nuestras necesidades en muchas otras, por lo que es necesario contar con otra alternativa (lo que veremos en un próximo artículo).

Por ahora nos conformaremos con enviar mensajes que contengan tanto notificación como datos desde la consola. Para añadir datos personalizados a un mensaje basta con indicar los pares de clave y valor en las opciones avanzadas del formulario de envío. Por seguir con nuestro ejemplo podríamos añadir las claves “usuario” y “estado” con dos valores de ejemplo:

fcm-consola-datos-personalizados

Si ejecutamos la aplicación en este momento, dejándola en primer plano, y enviamos el mensaje con los datos adicionales tal como se muestra en la imagen anterior, podremos ver en el log del sistema como se reciben correctamente los datos en el método onMessageReceived()

fcm-log-datos-recibidos

Podemos observar también como justo encima de los datos recibidos aparece también la información asociada a la notificación, lo que nos confirma que la Consola de Firebase no es capaz de enviar mensajes de datos sin carga de notificación (al menos en el momento de escribir este tutorial).

Aparentemente ya estaría todo resuelto con los mensajes de datos, pero aún nos queda un problema más por solucionar. Vamos a volver a enviar otro mensaje con datos personalizados desde la consola, pero esta vez con nuestra aplicación en segundo plano. ¿Qué ha ocurrido? El sistema ha generado automáticamente una notificación en la bandeja del sistema (por recibirse carga de notificación pero no encontrarse la aplicación en primer plano), pero sin embargo no se ha ejecutado el método onMessageReceived(), por lo que no hemos podido obtener los datos personalizados del mensaje.

Esto no es ningún error, es el funcionamiento esperado del sistema en estos casos. En el caso de mensajes mixtos (de notificación + datos), cuando la aplicación se encuentra en segundo plano el sistema gestionará por nosotros la información de notificación mostrando automáticamente el mensaje en la bandeja del sistema, pero condicionará la entrega a nuestra aplicación de los datos personalizados a que el usuario pulse sobre la notificación mostrada. Si el usuario pulsa sobre la notificación los datos se recibirán como extras del intent que iniciará nuestra aplicación. Podremos obtener estos datos por ejemplo en el método onCreate() llamando a getIntent() y sobre éste a getExtras().

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (getIntent().getExtras() != null) {
        Log.d(LOGTAG, "DATOS RECIBIDOS (INTENT)");
        Log.d(LOGTAG, "Usuario: " + getIntent().getExtras().getString("usuario"));
        Log.d(LOGTAG, "Estado: " + getIntent().getExtras().getString("estado"));
    }

    //...
}

Si volvemos a hacer nuevamente la prueba anterior, enviando el mensaje con datos cuando nuestra aplicación esté en segundo plano, ahora sí deberíamos ver en el log los datos recibidos al pulsar sobre la notificación generada automáticamente por el sistema.

fcm-log-datos-recibidos-intent

Es importante entender que si hubiéramos podido enviar mensajes de datos puros desde la consola no habría sido necesario hacer esto último, ya que los mensajes que únicamente incluyen carga de datos siempre se reciben en el método onMessageReceived(), independientemente de que la aplicación esté visible o en segundo plano.

A modo de resumen, la siguiente tabla muestra cuándo y donde se recibe la información de un mensaje dependiendo de su tipo y la situación actual de nuestra aplicación.

Situación Aplicación Notificación Datos Notificación + Datos
En primer plano onMessageReceived() onMessageReceived() onMessageReceived()
En segundo plano Bandeja del sistema onMessageReceived() Notificación: Bandeja del sistema
Datos: Extras del intent

Con esto finalizaríamos el apartado dedicado a los mensajes de datos y tan sólo nos quedaría por ahora buscar una alternativa a la consola de Firebase, que sea más completa, versátil e integrable con el resto de nuestro sistema. Lo veremos en el próximo artículo.

Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la página del curso en GitHub.

Curso Programación Android

Este artículo forma parte del Curso de Programación Android que tienes disponible de forma completamente gratuita en sgoliver.net

La entrada Notificaciones Push en Android: Firebase Cloud Messaging (4) aparece primero en sgoliver.net.