Aspectos avanzados de Android en Kotlin 01.2: Firebase Cloud Messaging en 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 en secuencia, pero no es obligatorio. Todos los codelabs del curso se detallan en la página de destino de Codelabs avanzados de Android en Kotlin.

Introducción

En el codelab anterior, agregaste notificaciones al temporizador de huevo que se crean y activan dentro de la app. Otro caso importante de notificaciones es el envío remoto de 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. Estas se pueden enviar a un dispositivo independientemente de si la app está en ejecución 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 acciones, en lugar de tener que verificar el estado de cada acción a diario.

Las notificaciones push utilizan el patrón publish/subscribe, que permite que las apps de backend envíen contenido relevante a clientes interesados. Sin un modelo de publicación y suscripción, los usuarios de tu app tendrían que comprobar periódicamente si hay actualizaciones disponibles. Este proceso es tedioso y poco confiable para los usuarios. Además, a medida que aumente la cantidad de clientes, estas verificaciones periódicas implicarían una gran carga de recursos de red y procesamiento para el servidor de tu app y el dispositivo de un usuario.

Al igual que con cualquier otro tipo de notificaciones, asegúrate de respetarlas con notificaciones push. Si el contenido de la notificación no es interesante o 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 Firebase para el desarrollo de apps para dispositivos móviles. Por lo general, es necesario configurar un servidor desde cero que pueda comunicarse con dispositivos móviles para activar notificaciones. Con Firebase Cloud Messaging, puedes enviar notificaciones a todos los usuarios de tu app que estén instalados o a un subconjunto de ellos, sin necesidad de configurar un servidor. Por ejemplo, puedes enviarles un recordatorio o ofrecerles una promoción especial, como un regalo de cortesía.Puedes enviar una notificación de manera remota a un solo dispositivo o a varios.

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

En este codelab, aprenderás a usar Firebase Cloud Messaging con el fin de enviar notificaciones push para tu app para Android y 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, trabaja 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 sobre AlarmManager
  • Cómo crear y enviar notificaciones con NotificationsManager

Qué aprenderás

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

Actividades

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

En este codelab, trabajarás en el código del codelab anterior Cómo usar notificaciones en apps para Android. En el codelab anterior, compilaste una app de temporizador de huevo que envía notificaciones cuando finaliza el temporizador de cocina. En este codelab, agregarás Firebase Cloud Messaging para enviar notificaciones push a los usuarios de tu app a fin de recordarles que coman huevos.

Para obtener la app de muestra, 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 selecciona o ingresa el Nombre del proyecto. Asigne el nombre fcm-codelab al proyecto.
  3. Haga clic en Continuar.
  4. Si desea omitir la configuración de Google Analytics, puede desactivar 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 la app porque no puedes agregar ni modificar este valor después de haber registrado la 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 la app.

  1. Haz clic en Descargar google-services.json a fin de obtener el archivo de configuración de Firebase para Android (google-services.json). Asegúrate de que no se hayan agregado caracteres adicionales y de que el archivo de configuración se llame exactamente google-services.json.
  2. Traslada el 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 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 de raíz (nivel de 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 (a nivel de app), agrega una línea para aplicar el complemento en la parte inferior 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) al proyecto para aprovechar las notificaciones push.

El código de 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.

Para probar su implementación, usará el Compositor de Notifications. El Compositor de Notifications es una herramienta que te permite redactar y enviar mensajes desde el sitio web de Firebase console.

  1. Abrir MyFirebaseMessagingService.kt
  2. Revise el archivo y, en particular, las siguientes funciones:
  • onNewToken(): Se llama automáticamente si el servicio está registrado en el manifiesto de Android. Se llama a esta función cuando ejecutas la app por primera vez y cada vez que Firebase emite un token nuevo para la app. Un token es una clave de acceso al proyecto de backend de Firebase. Se generará para el dispositivo cliente específico. Con este token, Firebase sabrá a qué cliente debe enviar los 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 se ejecuta tu app y Firebase le envía un mensaje a esta. Esta función recibe un objeto RemoteMessage que puede llevar una carga útil de notificación o de mensaje de datos. Más adelante en este codelab, obtendrás más información sobre las diferencias entre las notificaciones y las cargas útiles de mensajes de datos.

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

La consola de notificaciones te permite probar el envío de notificaciones. Para enviar un mensaje a un dispositivo específico a través de la consola, debes conocer el token de registro de ese dispositivo.

Cuando el backend de Firebase genera un token nuevo o actualizado, se llama a la función onNewToken(), con el token nuevo pasado como un argumento. Si deseas orientarte a un solo dispositivo o crear un grupo de dispositivos a los que deseas enviar un mensaje de transmisión, deberás extender FirebaseMessagingService y anular onNewToken() para acceder a este token.

  1. Abre AndroidManifest.xml y quita el comentario del siguiente código a fin de habilitar MyFirebaseMessagingService para la app de temporizador de huevo. Los metadatos del servicio en el manifiesto de Android registran MyFirebaseMessagingService como servicio y agregan un filtro de intents para que este reciba mensajes enviados de 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] -->

Se recomienda 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 para crear un canal.
// 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 los comentarios 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 (Ver > Ventanas de herramientas > Logcat). Deberías ver una línea de registro que muestra un token similar al siguiente. 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 antes. En ese caso, desinstalar la app te ayudará a recibir un token nuevo.

Ahora puedes probar enviando una notificación. Para enviar una notificación, deberás usar el Compositor de Notifications.

  1. Abre Firebase console y selecciona tu proyecto.
  2. Luego, seleccione Cloud Messaging en el menú de navegación de la izquierda.
  3. Haga 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 notificación, y selecciona Enviar mensaje de prueba. Aparecerá el diálogo emergente Probar en el dispositivo para que proporciones un token de registro de FCM.

  1. Copia el token de la app de logcat.

  1. Pega este token en el campo Agregar un token de registro de FCM dentro 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. El botón Test debe estar habilitado.

  1. En el dispositivo, coloca la app de Egg Timer en segundo plano.
  2. En la ventana emergente, haz clic en Probar.
  1. Cuando hagas clic en Probar, el dispositivo cliente de destino que ejecuta 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 administrar los mensajes de FCM cuando tu app está en primer plano).

Tarea: Envíe notificaciones de FCM a un tema

Los mensajes por temas de FCM se basan 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 consumirá la batería del teléfono, sino que también consumirá recursos de red innecesarios y creará una carga innecesaria en el servidor de su aplicación. En su lugar, un dispositivo cliente puede suscribirse y recibir notificaciones cuando se publiquen mensajes nuevos mediante 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 que les interesan al cliente. 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 meteorológicos y resultados deportivos. Para esta parte del codelab, crearás un tema "breakfast" para recordarles a los usuarios interesados de la app que coman huevos con su 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 se realiza correctamente, se llamará a la devolución de llamada OnCompleteListener con el mensaje de suscripción. Si el cliente no se suscribe, la devolución de llamada recibirá un mensaje de error.

En la app, suscribirás automáticamente a tus usuarios al tema del desayuno. En la mayoría de las apps de producción, sin embargo, es mejor darles a los usuarios control sobre los temas a los que deben suscribirse.

  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 que indicará si tu 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 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 de aviso que dice "Te suscribiste a un tema".

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

  1. Abre el Compositor de Notifications y selecciona Redactar notificación.
  2. Configure el Texto 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. En Programación, seleccione Ahora.

  1. Asegúrate de que la app se ejecute en segundo plano en el 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 reciba en todos los dispositivos suscritos a este tema.

Ahora, la app tiene los siguientes canales para recibir 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 notificaciones Egg y Breakfast, 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 siempre 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 procesa los mensajes en la app cliente, usa mensajes de datos en lugar de mensajes de notificación.

Para manejar 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. El objeto remoteMessage y la propiedad data pueden ser null.

  1. Abrir MyFirebaseMessagingService.
  2. Verifica 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 el código, vuelve a usar el Compositor de Notifications.

  1. Abre el Compositor de Notifications y crea un mensaje nuevo configurando su Target en el tema "breakfast"
  2. Esta vez, cuando llegue al paso 4, Opciones adicionales, configure las claves y las propiedades de valor Custom data de la siguiente manera:
  1. Clave: eggs
  2. Valor: 3

  1. Asegúrate de que la app se ejecute en primer plano. Si la app está en segundo plano, el mensaje de FCM activará una notificación automática, y la función onMessageReceived() recibirá solo 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: Administra los 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 la app se encuentra en segundo plano o en primer plano en ese dispositivo:

  • Si la app se ejecuta en segundo plano, si 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 controlará la carga cuando el usuario presione la notificación.
  • Si la app se ejecuta en primer plano, si la notificación del mensaje tiene una carga útil, la notificación no aparecerá automáticamente. La app necesita decidir cómo manejar la notificación en la función onMessageReceived(). Si el mensaje también tiene una carga útil de datos, la app controlará ambas cargas.

Para este codelab, debes recordarle al usuario de la app que desayune algunos huevos. No tienes planeado enviar datos, pero también quieres asegurarte de que la notificación de recordatorios aparezca siempre, 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 huevo, el mensaje de notificación se mostrará automáticamente si no se está ejecutando o está en segundo plano. Sin embargo, si la app se ejecuta 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, se activará automáticamente la función onMessageReceived() con este mensaje. Aquí es donde tu app puede controlar de forma silenciosa las cargas útiles de notificaciones y datos, o activar una notificación.

Para tu app, si quieres asegurarte de que el usuario reciba el recordatorio cuando la app esté en primer plano, implementemos 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 mediante 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 Compositor de Notifications, deberías ver una notificación como lo hiciste en la primera parte del codelab, independientemente de que la app esté en primer o segundo plano.

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

Curso de Udacity:

Documentación de Firebase:

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