Notificaciones en Android con Firebase y PHP

Aunque a priori parezca un tema complejo, la realidad es que con muy poco código y algo de configuración podemos tener toda la potencia de las notificaciones de Google en nuestra aplicación Android. En este artículo voy a contar mi reciente experiencia con Firebase, la mejorada plataforma de desarrollo de Google, y los problemas que tuve que solucionar para que todo funcionase correctamente.

El problema

El problema que tenía que resolver era que una aplicación Android recibiese una notificación cada vez que se añadía una noticia en una aplicación web desarrollada en PHP.

Tras un poco de investigación parecía que la herramienta necesaria para ello era Google Firebase, la plataforma de desarrollo de Google, que nos permite simplificar mucho el proceso de sincronización desde nuestra aplicación.

¡Vamos a ello!

La configuración

Vamos a partir de la base de que tenemos nuestro Android Studio perfectamente actualizado, configurado y un proyecto nuevo de Android abierto.

Lo primero que tenemos que hacer es configurar correctamente un proyecto de Firebase e incluir en nuestra aplicación las librerías necesarias.

Abrimos la ventana de Firebase desde Android Studio.

En la ventana que aparece seleccionamos la opción Cloud Messaging

Ahora pulsamos en la opción de “Set up Firebase Cloud Messaging”.

Pulsamos el botón del primer paso “Connect to Firebase” y se nos abrirá una nueva ventana.

Desde aquí podemos crear directamente un proyecto nuevo de Firebase o conectarnos a uno ya existente. Como de momento no hemos entrado en la consola de Firebase vamos a elegir la opción de Crear un nuevo proyecto.

Tras una pequeña espera, el proyecto estará creado y aparecerá el mensaje “Connected” donde antes estaba el botón.

Ahora vamos al paso 2, añadir las librerías al proyecto. Para eso pulsamos el botón “Add FCM to you app”. Aceptamos los cambios que nos propone en la ventana que se abre.

Una vez finalice podremos ver que en nuestro build.gradle (Module:app) aparecen nuevas dependencias. Si todo va correctamente y esperamos a que sincronice ya deberíamos de tener las librerías de Firebase incluidas en nuestro proyecto.

En este punto deberíamos de tener todo nuestro proyecto configurado en Android Studio. Si accedemos a la consola de Firebase nos aparecerá la siguiente ventana con el proyecto que hemos creado en los pasos anteriores.

Seleccionamos el proyecto EjemploNotificaciones y entramos en el panel principal del mismo con nuestra aplicación creada.

El código Java

Una vez configurado todo el proyecto es hora de teclear algo de código. Para que funcionen las notificaciones en Android es necesario crear un servicio que las gestione.

Para ello creamos la clase MyFirebaseMessagingService que extiende de la clase FirebaseMessagingService.

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

import java.util.Random;

public class MyFirebaseMessagingService extends FirebaseMessagingService {

    private NotificationManager notificationManager;
    private static final String ADMIN_CHANNEL_ID ="admin_channel";

    @Override
    public void onNewToken(String s) {

        /*
            En este método recibimos el 'token' del dispositivo.
            Lo necesitamos si vamos a comunicarnos con el dispositivo directamente.
        */

        super.onNewToken(s);
        Log.e("NEW_TOKEN",s);

        /*
            A partir de aquí podemos hacer lo que queramos con el token como
            enviarlo al servidor para guardarlo en una B.DD.

            Nosotros no haremos nada con el token porque no nos vamos a comunicar con un sólo
            dispositivo.

         */
    }

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

        // En este método recibimos el mensaje

        Intent notificationIntent;

        if(MenuPrincipal.isAppRunning){

            // Qué hacemos si la aplicación está en primer plano
            notificationIntent = new Intent(this, MenuPrincipal.class);

        }else{

            // Qué hacemos si la aplicación está en background
            notificationIntent = new Intent(this, MenuPrincipal.class);
        }
        notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

        final PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent,
                PendingIntent.FLAG_ONE_SHOT);

        notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        // Configuramos la notificación para Android Oreo o superior
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            setupChannels();
        }
        int notificationId = new Random().nextInt(60000);

        // Creamos la notificación en si

        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, ADMIN_CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_notifications_active_black_24dp)  //a resource for your custom small icon
                .setContentTitle(remoteMessage.getData().get("title")) //the "title" value you sent in your notification
                .setContentText(remoteMessage.getData().get("message")) //ditto
                .setAutoCancel(true)  //dismisses the notification on click
                .setContentIntent(pendingIntent)
                .setSound(defaultSoundUri);

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        notificationManager.notify(notificationId /* ID of notification */, notificationBuilder.build());
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    private void setupChannels(){
        CharSequence adminChannelName = getString(R.string.notifications_admin_channel_name);
        String adminChannelDescription = getString(R.string.notifications_admin_channel_description);

        NotificationChannel adminChannel;
        adminChannel = new NotificationChannel(ADMIN_CHANNEL_ID, adminChannelName, NotificationManager.IMPORTANCE_LOW);
        adminChannel.setDescription(adminChannelDescription);
        adminChannel.enableLights(true);
        adminChannel.setLightColor(Color.RED);
        adminChannel.enableVibration(true);
        if (notificationManager != null) {
            notificationManager.createNotificationChannel(adminChannel);
        }
    }
}

Como vemos en el código, sólo utilizamos dos métodos:

  1. En el método onNewToken recibimos del sistema de notificaciones un “token” que es un identificador de nuestro dispositivo. Este “token” puede ser utilizado por nuestra aplicación (para enviarle notificaciones a ese dispositivo concreto) o no. En nuestro caso, como vamos a enviar notificaciones a varios dispositivos, no es necesario hacer nada con el “token”. Es importante saber que la aplicación llamará a este método cuando se reciba un “token” nuevo que no es cada vez que se ejecute la aplicación sino la primera vez que lo hace. Si queremos hacer pruebas al respecto tendremos que desinstalar la aplicación de nuestro dispositivo e instalarla de nuevo.
  2. En el método onMessageReceived recibimos el mensaje que nos envía el sistema y a continuación creamos la notificación en si que se mostrará en la barra de Android.

Ahora es necesario declarar este servicio en nuestro AndroidManifest.xml

<service
    android:name=".MyFirebaseMessagingService"
    android:permission="com.google.android.c2dm.permission.SEND">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
    </intent-filter>
</service>

Así como añadirle los permisos siguientes:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

Pues ya está. Si ahora entramos en nuestra consola de Firebase y nos dirigimos a la sección Cloud Messaging ya podremos enviar la primera notificación a nuestra aplicación.

Pulsamos en el botón Send your first message y rellenamos los cuadros de Título de la notificación y Texto de la notificación. 

Pulsamos en Siguiente y, dentro del apartado, Orientación elegimos nuestra aplicación.

Los demás apartados son opciones. Pulsamos el botón Revisar de la parte inferior derecha y el botón Publicar de la ventana que se abre.

La notificación debería de llegar a nuestro dispositivo sin problemas.

¡OJO! En este punto tuve un serio problema: las notificaciones a veces llegaban y otras no… tras pelearme con configuraciones y código descubrí que tenía que introducir mi aplicación en una lista de aplicaciones protegidas en mi teléfono móvil. Sin incluirla, Android cerraba la aplicación cuando se quedaba en background y dejaba de recibir las notificaciones.

El código PHP

Ha llegado el momento de enviar nuestras propias notificaciones desde PHP. Para ello primero tenemos que saber que podemos enviar dos tipos de notificaciones:

  • Notificaciones a un dispositivo concreto (para ello se utilizaría el “token” recibido en el servicio que creamos anteriormente).
  • Notificaciones a un grupo de dispositivos que estén suscritos a un “topic”. Esto permite que los usuarios de una aplicación reciban, por ejemplo, sólo las notificaciones de los temas de su interés (deportes, viajes, etc.).

En mi caso necesitaba enviar la notificación a todos los dispositivos que tuvieran la aplicación instalada pero, desgraciadamente, Firebase no ofrece esa funcionalidad. Había dos aproximaciones a este problema: o enviaba al servidor y guardaba en la base de datos los tokens recibidos en el método onNewToken del servicio FirebaseMessagingService o suscribía a todas las instancias de mi aplicación a un “topic” genérico. Opté por esta última opción.

Volvemos al Java

Para suscribir a una aplicación a un “topic” utilizamos el siguiente código:

FirebaseMessaging.getInstance().subscribeToTopic("topic_general");

Ahora la duda era dónde situar ese código. Para ello, vi que la mejor opción era crear una clase que extienda de Application. Este tipo de clases se ejecutan al inicio de toda aplicación Android, ANTES de cualquier clase, servicio o proceso.

Creamos entonces la clase NotificationSuscriptor

package es.angelcasas.meteoferrolterra;

import android.app.Application;

import com.google.firebase.messaging.FirebaseMessaging;

public class NotificationSuscriptor extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        FirebaseMessaging.getInstance().subscribeToTopic("noticias_meteo_ferrolterra");
    }
}

Y por supuesto, también tenemos que declararla en nuestro AndroidManifest.xml pero en este caso es simplemente añadiendo

android:name=".NotificationSuscriptor"

a nuestro ya existente tag <application. O sea, quedaría algo así:

<application
    android:name=".NotificationSuscriptor"
    android:allowBackup="true"
    android:icon="@drawable/icono"
    android:label="@string/app_name"
    android:roundIcon="@drawable/icono"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity
        android:name=".MenuPrincipal"
        android:screenOrientation="portrait">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <service
        android:name=".MyFirebaseMessagingService"
        android:permission="com.google.android.c2dm.permission.SEND">
        <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT" />
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
        </intent-filter>
    </service>
</application>

En mi caso, como podemos ver, sólo tenía una Activity llamada MenuPrincipal.

Si ejecutamos ahora nuestra app ya la tendríamos suscrita al topic “topic_general”.

Volvemos al PHP

Creamos en nuestra aplicación PHP un método para enviar la notificación con un título recibido por parámetro:

public function enviarNotificacion() {

    // Cargamos los datos de la notificacion en un Array
    $notification = array();
    $notification['title'] = 'Título de la notificación';
    $notification['message'] = 'Mensaje de la notificación';
    $notification['image'] = '';
    $notification['action'] = '';
    $notification['action_destination'] = '';            

    $topic = "topic_general";

    $fields = array(
        'to' => '/topics/' . $topic,
        'data' => $notification,
    );

    // Set POST variables
    $url = 'https://fcm.googleapis.com/fcm/send';

    $headers = array(
                'Authorization: key=AQUÍ_PONEMOS_NUESTRA_CLAVE_DEL_SERVIDOR',
                'Content-Type: application/json'
                );
                
    // Open connection
    $ch = curl_init();

    // Set the url, number of POST vars, POST data
    curl_setopt($ch, CURLOPT_URL, $url);

    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    // Disabling SSL Certificate support temporarily
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));       
    
    $result = curl_exec($ch);

    if($result === FALSE) {

        die('Curl failed: ' . curl_error($ch));
    }

    // Close connection
    curl_close($ch);
}

Lo único que tenemos que modificar en este código es la línea en donde pone AQUÍ_PONEMOS_NUESTRA_CLAVE_DEL_SERVIDOR. Este valor lo tenemos que sustituir por el que tenemos en la configuración de nuestra consola de Firebase.

Entramos en la consola, en las opciones de configuración del proyecto.

En la pestaña Cloud Messaging

Será el valor que se encuentra al lado de la leyenda Clave del Servidor

¡Y ahora si!. Ya deberíamos ser capaces de enviar notificaciones desde PHP a nuestra aplicación Android.

6 comentarios

  1. Long time reader, first time commenter — so, thought I’d drop a comment..

    — and at the same time ask for a favor.

    Your wordpress site is very simplistic – hope you don’t mind me asking what theme you’re
    using? (and don’t mind if I steal it? :P)

    I just launched my small businesses site –also built in wordpress like yours– but the theme slows (!) the site down quite a bit.

    In case you have a minute, you can find it by searching for
    “royal cbd” on Google (would appreciate any feedback)

    Keep up the good work– and take care of yourself during the coronavirus scare!

    ~Justin

  2. Hola! Muy interesante, solo me ha quedado una duda: en caso de hacerlo con la key del usuario, deberia incluir en mi base de datos un campo key, y en “onNewToken ” debería introducirlo en la baase de datos, ¿pero como obtengo el usuario al que hacerle esa actualización? En caso de yo tener un “id_usuario” por ejemplo para identificarlo, ¿como obtenog en este código ese id para actualizar su key y así notificarle?

    Muchas gracias!!

  3. Muy buena explicación!!! Pero una duda, para poder pasar la key a la base de datos ern caso de querer hacerlo a algún usuario concreto, ¿como puedo saber a que usuario concreto debo modificarle su valor ‘key’? Quiero decir, cuando dices “A partir de aquí podemos hacer lo que queramos con el token como
    enviarlo al servidor para guardarlo en una B.DD.”, como sé el identificador del usuario en la base datos?? Muchas gracias.

    1. Muchas gracias por el comentario! Con respecto a tu pregunta cuando la aplicación se ejecuta por primera vez y se llama al método onNewToken no tendrás aun al usuario en la base de datos ya que se supone que se acaba de instalar la aplicación y ejecutarla por primera vez. En ese método cuando tienes que crear una entrada en tu tabla de usuarios con el token que recibes y un id_usuario que le asignes. Así siempre tendrás relacionados el token con el id del usuario.

Los comentarios están cerrados.