Aspectos avanzados de Android en Kotlin 01.2: Firebase Cloud Messaging para Android

Este codelab es parte del curso Aspectos avanzados de Android en Kotlin. Aprovecharás al máximo este curso si trabajas con los codelabs de forma secuencial, aunque no es obligatorio. Todos los codelabs del curso se indican en la página de destino de los codelabs de Aspectos avanzados de Android en Kotlin.

Introducción

En el codelab anterior, agregaste notificaciones al temporizador de huevos que se crean y activan dentro de la app. Otro caso de uso importante de las notificaciones es enviar de forma remota notificaciones push que se pueden recibir incluso cuando la app no se está ejecutando.

¿Qué es una notificación push?

Las notificaciones push son notificaciones que el servidor "envía" a los dispositivos móviles. Se pueden entregar a un dispositivo independientemente de si tu app se está ejecutando o no.

Las notificaciones push son una excelente manera de informar a los usuarios sobre una actualización o recordarles una tarea o función. Imagina que esperas a que un producto vuelva a estar en stock. Con una notificación push, una app de compras puede informarte sobre las actualizaciones de stock en lugar de que tengas que verificar el estado del stock todos los días.

Las notificaciones push utilizan el patrón de publicación y suscripción, que permite que las apps de backend envíen contenido pertinente a los clientes interesados. Sin un modelo de publicación y suscripción, los usuarios de tu app deberían verificar periódicamente si hay actualizaciones en ella. Este proceso es tedioso y poco confiable para los usuarios. Además, a medida que aumenta la cantidad de clientes, estas verificaciones periódicas generarían una carga demasiado grande en los recursos de procesamiento y redes, tanto para el servidor de tu app como para el dispositivo del usuario.

Al igual que con todos los demás tipos de notificaciones, asegúrate de respetar a tus usuarios con las notificaciones push. Si el contenido de la notificación no es interesante ni oportuno para el usuario, puede desactivar fácilmente todas las notificaciones de tu app.

¿Qué es Firebase Cloud Messaging?

Firebase Cloud Messaging forma parte de la plataforma de Firebase para el desarrollo de aplicaciones para dispositivos móviles. Por lo general, deberás configurar un servidor desde cero que pueda comunicarse con los dispositivos móviles para activar notificaciones. Con Firebase Cloud Messaging, puedes enviar notificaciones a todos los usuarios que instalaron tu app o a un subconjunto de ellos sin configurar un servidor. Por ejemplo, puedes enviarles a los usuarios un recordatorio o darles una promoción especial, como un regalo gratis.Puedes enviar una notificación de forma remota a uno o varios dispositivos.

También puedes usar Firebase Cloud Messaging para transferir datos desde tu app de backend o desde un proyecto de Firebase a tus usuarios.

En este codelab, aprenderás a usar Firebase Cloud Messaging para enviar notificaciones push a tu app para Android, así como a enviar datos.

Si a medida que avanzas con este codelab encuentras algún problema (errores de código, errores gramaticales, texto poco claro, etc.), infórmalo mediante el vínculo Informa un error que se encuentra en la esquina inferior izquierda del codelab.

Conocimientos que ya deberías tener

Debes estar familiarizado con lo siguiente:

  • Cómo crear apps para Android en Kotlin En particular, trabajar con el SDK de Android
  • Cómo diseñar tu app con componentes de arquitectura y vinculación de datos
  • Conocimientos básicos sobre los receptores de emisión
  • Conocimientos básicos de AlarmManager
  • Cómo crear y enviar notificaciones con NotificationManager

Qué aprenderás

  • Cómo enviar mensajes al usuario a través de Firebase Cloud Messaging
  • Cómo enviar datos desde un backend a tu app con mensajes de datos, que forman parte de Firebase Cloud Messaging

Actividades

  • Agrega notificaciones push a la app de inicio.
  • Maneja Firebase Cloud Messaging mientras se ejecuta tu app.
  • Transferir datos con Firebase Cloud Messaging

En este codelab, trabajarás con el código del codelab anterior sobre el uso de notificaciones en apps para Android. En el codelab anterior, compilaste una app de temporizador de huevos que envía notificaciones cuando se acaba el tiempo de cocción. En este codelab, agregarás Firebase Cloud Messaging para enviar notificaciones push a los usuarios de tu app y recordarles que coman huevos.

Para obtener la app de ejemplo, puedes hacer lo siguiente:

Clona el repositorio desde GitHub y cambia a la rama starter:

$  git clone https://github.com/googlecodelabs/android-kotlin-notifications-fcm


También puedes descargar el repositorio como un archivo ZIP, descomprimirlo y abrirlo en Android Studio.

Download Zip

Paso 1: Crea un proyecto de Firebase

Antes de poder agregar Firebase a tu app para Android, debes crear un proyecto de Firebase y conectarlo a la app.

  1. Accede a Firebase console.
  2. Haz clic en Agregar proyecto y, luego, selecciona o ingresa el Nombre del proyecto. Asigna el nombre fcm-codelab al proyecto.
  3. Haz clic en Continuar.
  4. Puedes omitir la configuración de Google Analytics desactivando el botón Habilitar Google Analytics para este proyecto .
  5. Haz clic en Crear proyecto para terminar de configurar el proyecto de Firebase.

Paso 2: Registra tu app con Firebase

Ahora que tienes un proyecto de Firebase, puedes agregarle tu app para Android.

  1. En el centro de la página de descripción general del proyecto en Firebase console, haz clic en el ícono de Android para iniciar el flujo de trabajo de configuración.

  1. En el campo Nombre del paquete de Android, ingresa com.example.android.eggtimernotifications.
  2. Haz clic en Registrar app.

Importante: Asegúrate de ingresar el ID correcto de tu app, ya que no podrás agregar ni modificar este valor después de registrar tu app en el proyecto de Firebase.

Paso 3: Agrega el archivo de configuración de Firebase a tu proyecto

Agrega el archivo de configuración de Firebase para Android a tu app.

  1. Haz clic en Descargar google-services.json para obtener el archivo de configuración de Firebase para Android (google-services.json). Asegúrate de que el archivo de configuración no tenga caracteres adicionales y se llame google-services.json exactamente.
  2. Transfiere tu archivo de configuración al directorio del módulo (nivel de app) de tu app.

Paso 4: Configura tu proyecto de Android para habilitar los productos de Firebase

Para habilitar los productos de Firebase en tu app, debes agregar el complemento de google-services a tus archivos Gradle.

  1. En el archivo Gradle (build.gradle) de nivel raíz (nivel del proyecto), verifica que tengas el repositorio Maven de Google.
  2. Luego, agrega reglas para incluir el complemento de Google Services.

build.gradle

buildscript {

  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository
  }

  dependencies {
    // ...

    // Add the following line:
    classpath 'com.google.gms:google-services:4.3.2'  // Google Services plugin
  }
}

allprojects {
  // ...

  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository
    // ...
  }
}
  1. En el archivo Gradle (generalmente app/build.gradle) de tu módulo (nivel de app), agrega una línea para aplicar el complemento al final del archivo.

app/build.gradle

apply plugin: 'com.android.application'

android {
  // ...
}

// Add the following line to the bottom of the file:
apply plugin: 'com.google.gms.google-services'  // Google Play services Gradle plugin

En esta tarea, agregarás Firebase Cloud Messaging (FCM) a tu proyecto para usar notificaciones push.

El código del servicio de Android para FCM de este codelab se proporciona en MyFirebaseMessagingService.kt. En los siguientes pasos, agregarás código a tu app para Android.

Usarás el Compositor de Notifications para probar tu implementación. El Compositor de Notifications es una herramienta que te ayuda a redactar y enviar mensajes desde el sitio web de Firebase console.

  1. Abrir MyFirebaseMessagingService.kt
  2. Inspecciona el archivo y, en particular, las siguientes funciones:
  • onNewToken(): Se llama automáticamente si tu servicio está registrado en el manifiesto de Android. Se llama a esta función cuando ejecutas tu app por primera vez y cada vez que Firebase emite un nuevo token para tu app. Un token es una clave de acceso a tu proyecto de backend de Firebase. Se genera para tu dispositivo cliente específico. Con este token, Firebase sabe a qué cliente debe enviar mensajes el backend. Firebase también sabe si este cliente es válido y tiene acceso a este proyecto de Firebase.
  • onMessageReceived: Se llama cuando tu app se está ejecutando y Firebase le envía un mensaje. Esta función recibe un objeto RemoteMessage que puede contener una carga útil de notificación o de mensaje de datos. Aprenderás más sobre las diferencias entre las cargas útiles de notificaciones y mensajes de datos más adelante en este codelab.

Paso 1: Envía notificaciones de FCM a un solo dispositivo

La consola de Notifications te permite probar el envío de una notificación. Para enviar un mensaje a un dispositivo específico con la consola, debes conocer el token de registro de ese dispositivo.

Cuando el backend de Firebase genera un token nuevo o actualizado, se llamará a la función onNewToken(), y se pasará el token nuevo como argumento. Si quieres segmentar un solo dispositivo o crear un grupo de dispositivos al que quieras enviar un mensaje de transmisión, deberás acceder a este token extendiendo FirebaseMessagingService y anulando onNewToken().

  1. Abre AndroidManifest.xml y quita el comentario del siguiente código para habilitar MyFirebaseMessagingService para la app de temporizador de huevos. Los metadatos del servicio en el manifiesto de Android registran MyFirebaseMessagingService como un servicio y agregan un filtro de intents para que este servicio reciba mensajes enviados desde FCM. La última parte de los metadatos declara breakfast_notification_channel_id como default_notification_channel_id para Firebase. Usarás este ID en el siguiente paso.
<!-- AndroidManifest.xml -->
<!-- TODO: Step 3.0 uncomment to start the service  -->

        <service
                android:name=".MyFirebaseMessagingService"
                android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT"/>
            </intent-filter>
        </service>
        <!-- [START fcm_default_icon] -->
        <!--
 Set custom default icon. This is used when no icon is set for incoming notification messages.
             See README(https://goo.gl/l4GJaQ) for more.
        -->
        <meta-data
                android:name="com.google.firebase.messaging.default_notification_icon"
                android:resource="@drawable/common_google_signin_btn_icon_dark"/>
        <!--
 Set color used with incoming notification messages. This is used when no color is set for the incoming
             notification message. See README(https://goo.gl/6BKBk7) for more.
        -->
        <meta-data
                android:name="com.google.firebase.messaging.default_notification_color"
                android:resource="@color/colorAccent"/> <!-- [END fcm_default_icon] -->
        <!-- [START fcm_default_channel] -->
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_channel_id"
            android:value="@string/breakfast_notification_channel_id" />
        <!-- [END fcm_default_channel] -->

Es una buena idea crear un nuevo canal de notificaciones para FCM, ya que es posible que los usuarios quieran habilitar o inhabilitar el temporizador de huevos o las notificaciones push de FCM por separado.

  1. Abre ui/EggTimerFragment.kt . En onCreateView(), agrega el siguiente código de creación de canales.
// EggTimerFragment.kt

   // TODO: Step 3.1 create a new channel for FCM
    createChannel(
        getString(R.string.breakfast_notification_channel_id),
        getString(R.string.breakfast_notification_channel_name)
    )
  1. Abre MyFirebaseMessagingService.kt y quita el comentario de la función onNewToken(). Se llamará a esta función cuando se genere un token nuevo.
// MyFirebaseMessagingService.kt

   // TODO: Step 3.2 log registration token
    // [START on_new_token]
    /**
     * Called if InstanceID token is updated. This may occur if the security of
     * the previous token had been compromised. Note that this is called when the     
     * InstanceID token is initially generated so this is where you would retrieve     
     * the token.
     */
    override fun onNewToken(token: String?) {
        Log.d(TAG, "Refreshed token: $token")

        // If you want to send messages to this application instance or
        // manage this apps subscriptions on the server side, send the
        // Instance ID token to your app server.
        sendRegistrationToServer(token)
    }
    // [END on_new_token]
  1. Ejecuta la app de temporizador de huevos.
  2. Observa logcat (View > Tool Windows > Logcat). Deberías ver una línea de registro que muestre tu token, similar a la que se muestra a continuación. Este es el token que necesitas para enviar un mensaje a este dispositivo. Solo se llama a esta función cuando se crea un token nuevo.
2019-07-23 13:09:15.243 2312-2459/com.example.android.eggtimernotifications D/MyFirebaseMsgService: Refreshed token: f2esflBoQbI:APA91bFMzNNFaIskjr6KIV4zKjnPA4hxekmrtbrtba2aDbh593WQnm11ed54Mv6MZ9Yeerver7pzgwfKx7R9BHFffLBItLEgPvrtF0TtX9ToCrXZ5y7Hd-m

Nota: Si no ves el token en los mensajes de logcat, es posible que tu app ya lo haya recibido. En ese caso, desinstalar la app te ayudará a recibir un token nuevo.

Ahora puedes enviar una notificación para realizar una prueba. Para enviar una notificación, usarás el Compositor de Notifications.

  1. Abre Firebase console y selecciona tu proyecto.
  2. A continuación, selecciona Cloud Messaging en la navegación de la izquierda.
  3. Haz clic en Send your first message.

  1. Ingresa Time for Breakfast! como título de la notificación y Don't forget to eat eggs! como texto de la notificación, y selecciona Enviar mensaje de prueba. Aparecerá el diálogo emergente Test on device, en el que se te pedirá que proporciones un token de registro de FCM.

  1. Copia el token de tu app desde Logcat.

  1. Pega este token en el campo Agregar un token de registro de FCM de la ventana emergente y, luego, haz clic en el botón Agregar junto al token.
  2. En la lista de casillas de verificación que aparece, selecciona el token. Se debería habilitar el botón Probar.

  1. En tu dispositivo, pon la app de Temporizador de huevos en segundo plano.
  2. En la ventana emergente, haz clic en Test.
  1. Después de hacer clic en Probar, el dispositivo cliente de destino que tiene tu app en segundo plano debería recibir la notificación en la bandeja de notificaciones del sistema. (Más adelante, verás cómo controlar los mensajes de FCM cuando tu app esté en primer plano).

Tarea: Envía notificaciones de FCM a un tema

La mensajería por temas de FCM se basa en el modelo de publicación y suscripción.

Una app de mensajería puede ser un buen ejemplo del modelo Publicar/Suscribirse. Imagina que una app busca mensajes nuevos cada 10 segundos. Esto no solo agotará la batería del teléfono, sino que también usará recursos de red innecesarios y generará una carga innecesaria en el servidor de la app. En cambio, un dispositivo cliente puede suscribirse y recibir notificaciones cuando se envían mensajes nuevos a través de tu app.

Los temas te permiten enviar un mensaje a varios dispositivos que hayan aceptado ese tema en particular. Para los clientes, los temas son fuentes de datos específicas en las que están interesados. Para el servidor, los temas son grupos de dispositivos que aceptaron recibir actualizaciones sobre una fuente de datos específica. Los temas se pueden usar para presentar categorías de notificaciones, como noticias, pronósticos del clima y resultados deportivos. En esta parte del codelab, crearás un tema "desayuno" para recordarles a los usuarios de la app interesados que coman huevos en el desayuno.

Para suscribirse a un tema, la app cliente llama a la función subscribeToTopic() de Firebase Cloud Messaging con el nombre del tema breakfast. Esta llamada puede tener dos resultados. Si el llamador tiene éxito, se llamará a la devolución de llamada OnCompleteListener con el mensaje suscrito. Si el cliente no se puede suscribir, la devolución de llamada recibirá un mensaje de error.

En tu app, suscribirás automáticamente a los usuarios al tema del desayuno. Sin embargo, en la mayoría de las apps de producción, es mejor que los usuarios controlen a qué temas se suscriben.

  1. Abre EggTimerFragment.kt y busca la función subscribeTopic() vacía.
  2. Obtén una instancia de FirebaseMessaging y llama a la función subscibeToTopic() con el nombre del tema.
  3. Agrega un addOnCompleteListener para recibir una notificación de FCM sobre si la suscripción se realizó correctamente o no.
// EggTimerFragment.kt

   // TODO: Step 3.3 subscribe to breakfast topic
    private fun subscribeTopic() {
        // [START subscribe_topics]
        FirebaseMessaging.getInstance().subscribeToTopic(TOPIC)
            .addOnCompleteListener { task ->
                var msg = getString(R.string.message_subscribed)
                if (!task.isSuccessful) {
                    msg = getString(R.string.message_subscribe_failed)
                }
                Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
            }
        // [END subscribe_topics]
    }
  1. Llama a la función subscribeTopic() para suscribirte a un tema cuando se inicie la app. Desplázate hacia arriba hasta onCreateView() y agrega una llamada a subscribeTopic().
// EggTimerFragment.kt

   // TODO: Step 3.4 call subscribe topics on start
    subscribeTopic()

    return binding.root
  1. Para suscribirte al tema del desayuno, vuelve a ejecutar la app. Deberías ver un mensaje emergente que diga "Te suscribiste al tema".

Ahora puedes probar el envío de mensajes a un tema:

  1. Abre el Compositor de Notificationsy selecciona Redactar notificación.
  2. Establece el Título de la notificación y el Texto de la notificación como antes.
  3. Esta vez, en lugar de enviar el mensaje a un solo dispositivo, haz clic en Tema en Destino y, luego, ingresa breakfast como el tema del mensaje.

  1. Selecciona Ahora para programar.

  1. Asegúrate de que la app se esté ejecutando en segundo plano en tu dispositivo de prueba.
  1. Haz clic en Revisar y, luego, en Publicar. Si puedes ejecutar la app en más de un dispositivo, puedes probar y observar que la notificación se recibe en todos los dispositivos suscritos a este tema.

Ahora, la app tiene los siguientes canales para las notificaciones: Huevo y Desayuno. En un dispositivo cliente, mantén presionado el ícono de la app, selecciona Información y haz clic en Notificaciones. Deberías ver los canales de notificación Huevo y Desayuno, como se muestra en la siguiente captura de pantalla. Si anulas la selección del canal Breakfast, tu app no recibirá ninguna notificación enviada a través de este canal.

Cuando uses notificaciones, recuerda que los usuarios pueden desactivar cualquier canal de notificaciones en cualquier momento.

Paso 1: Mensajes de datos

Los mensajes de FCM también pueden contener una carga útil de datos que procese los mensajes en la app cliente. En este caso, usa mensajes de datos en lugar de mensajes de notificación.

Para controlar los mensajes de datos, debes controlar la carga útil de datos en la función onMessageReceived() de MyFirebaseMessagingService. La carga útil se almacena en la propiedad data del objeto remoteMessage. Tanto el objeto remoteMessage como la propiedad data pueden ser null.

  1. Abrir MyFirebaseMessagingService.
  2. Comprueba si la propiedad data del objeto remoteMessage tiene algún valor y, luego, imprime los datos en el registro.
// MyFirebaseMessagingService.kt

    // [START receive_message]
    override fun onMessageReceived(remoteMessage: RemoteMessage?) {
        // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
        Log.d(TAG, "From: ${remoteMessage?.from}")
        
       // TODO: Step 3.5 check messages for data
        // Check if the message contains a data payload.
        remoteMessage?.data?.let {
            Log.d(TAG, "Message data payload: " + remoteMessage.data)
        }

    }
    // [END receive_message]

Para probar tu código, puedes volver a usar el Compositor de Notifications.

  1. Abre el Compositor de Notifications, crea un mensaje nuevo y configura su destino en el tema "breakfast".
  2. Esta vez, cuando llegues al paso 4, Opciones adicionales, establece las propiedades de clave y valor de Datos personalizados de la siguiente manera:
  1. Clave: eggs
  2. Valor: 3

  1. Asegúrate de que la app se esté ejecutando en primer plano. Si tu app está en segundo plano, el mensaje de FCM activará una notificación automática y la función onMessageReceived() solo recibirá el objeto remoteMessage cuando el usuario haga clic en la notificación.
  2. Envía el mensaje desde el Compositor de Notifications y observa el registro de mensajes de datos que aparece en logcat.

Paso 2: Cómo controlar mensajes en primer y segundo plano

Cuando un dispositivo cliente que ejecuta tu app recibe un mensaje que incluye cargas de notificaciones y de datos, el comportamiento de la app depende de si se encuentra en segundo plano o en primer plano en ese dispositivo:

  • Si la app se ejecuta en segundo plano y el mensaje tiene una carga útil de notificación, la notificación se muestra automáticamente en la bandeja de notificaciones. Si el mensaje también tiene una carga útil de datos, la app la controlará cuando el usuario presione la notificación.
  • Si la app se ejecuta en primer plano y la notificación del mensaje tiene una carga útil de notificación, la notificación no aparecerá automáticamente. La app debe decidir cómo controlar la notificación en la función onMessageReceived(). Si el mensaje también tiene una carga útil de datos, la app controlará ambas cargas útiles.

Para los fines de este codelab, quieres recordarle al usuario de la app que coma huevos para el desayuno. No planeas enviar datos, pero también quieres asegurarte de que la notificación de recordatorio siempre aparezca, independientemente de si la app está en primer o segundo plano.

Cuando envías un mensaje de FCM a dispositivos en los que está instalada la app de temporizador de huevos, el mensaje de notificación se mostrará automáticamente si la app no se está ejecutando o está en segundo plano. Sin embargo, si la app se está ejecutando en primer plano, la notificación no se muestra automáticamente. En cambio, el código de la app decide qué hacer con el mensaje. Si la app está en primer plano cuando recibe un mensaje de FCM, la función onMessageReceived() se activará automáticamente con el mensaje de FCM. Aquí es donde tu app puede controlar de forma silenciosa las cargas útiles de datos y notificaciones, o bien activar una notificación.

En el caso de tu app, debes asegurarte de que el usuario reciba el recordatorio cuando la app esté en primer plano, por lo que implementaremos código para activar una notificación:

  1. Vuelve a abrir la función onMessageReceived() en MyFirebaseMessagingService.
  2. Inmediatamente después del código que agregaste recientemente para verificar el mensaje de datos, agrega el siguiente código, que envía una notificación con el framework de notificaciones.
// MyFirebaseMessagingService.kt

    // TODO: Step 3.6 check messages for notification and call sendNotification
    // Check if the message contains a notification payload.
    remoteMessage.notification?.let {
        Log.d(TAG, "Message Notification Body: ${it.body}")
        sendNotification(it.body as String)
    }
  1. Si vuelves a ejecutar la app y envías una notificación con el Notifications Composer, deberías ver una notificación como la que veías en la primera parte del codelab, independientemente de si la app está en primer plano o en segundo plano.

El código de la solución se encuentra en la rama principal del código que descargaste.

Curso de Udacity:

Documentación de Firebase:

Para obtener vínculos a otros codelabs de este curso, consulta la página de destino de los codelabs de Aspectos avanzados de Android en Kotlin.