Tutoriales - Notificaciones Push

En este tutorial te cuento cómo implementar NOTIFICACIONES PUSH (PUSH NOTIFICATIONS) en Android (Java), mediante Firebase (el servicio gratuito  Firebase Cloud Messaging FCM), con un Servidor (backend) en PHP, el sistema completo, paso a paso.

Implementación de Notificaciones Push en Android paso a paso

Vamos a usar Firebase Cloud Messaging (FCM), el servicio gratuito de notificaciones de Firebase.

La arquitectura FCM tiene tres patas:

  • la app, nuestra aplicación
  • el servidor, el backend
  • FCM, que nos da una API, una lista de servicios que vamos a poder usar para implementar las notificaciones, tanto del lado de la app como del lado del servidor.
singleton

¿Cómo es la secuencia?

La secuencia consta de 3 pasos, bien definidos, que voy a explicar a continuación:

Paso 1: registrar en FCM

Partimos de la app, el dispositivo que tiene instalada la app debe registrarse en FCM. Envía una solicitud a registro a invocando un método de la API. FCM lo recibe, lo registra y devuelve un token, un código identificador único de dispositivo, un deviceid. Con ese ID vamos a poder enviarle notificaciones push a ese dispositivo.

singleton

Paso 2: guardar el deviceid en nuestro servidor

Necesitamos guardarlo para saber a quién corresponde. La app lo envía a nuestro servidor, quién lo recibe y lo guarda, por ejemplo en una tabla de base de datos, y le devuelve un “ok listo, ya estás registrado”

singleton

Paso 3: enviar notificación

Finalmente, cuando queremos mandar una notificación, nuestro servidor le dice a FCM: “che FCM, mandale la notificación, mandale este mensaje, a este dispositivo”, le tiene que mandar los dos parámetros: el texto y el device ID. FCM lo va a recibir, le va a enviar la notificación, y le va a devolver “¡listo ya lo envié!”.

singleton

Así funciona las tres patas app, servidor y FCM. También hay envío por broadcast a canales, pero este es el modo que te va a servir para cualquier proyecto.

¡Vamos al CÓDIGO!

Creamos la APP

Primero creamos la app Android desde Android Studio. Cargamos nombre de paquete, es importante el nombre del paquete porque es el identificador de la APP.  

singleton

Creamos proyecto en Firebase

En Firebase creamos el proyecto “app notificaciones”, vamos a configuración del proyecto y agregamos la aplicación. Ponemos el nombre del paquete, tiene que ser el mismo paquete de la app.

singleton

Descargamos el archivo y lo ubicamos en la carpeta App.

singleton

Agregamos librerías a la App

Seguimos los pasos que figuran en Firebase al crear el proyecto, y agregamos las librerías en los dos Build.gradle, así quedaría el proyecto:

/build.gradle

singleton

En código:


  // Top-level build file where you can add configuration options common to all sub-projects/modules.
  plugins {
      id ‘com.android.application’ version ‘7.3.1’ apply false
      id ‘com.android.library’ version ‘7.3.1’ apply false
      id ‘com.google.gms.google-services’ version ‘4.3.13’ apply false
  }
              

/app/build.gradle

singleton
singleton

Archivo completo:


    plugins {
        id ‘com.android.application’
        id ‘com.google.gms.google-services’
    }
    
    android {
        namespace ‘app.unsimpledev.appnotificaciones’
        compileSdk 32
    
        defaultConfig {
            applicationId “app.unsimpledev.appnotificaciones”
            minSdk 26
            targetSdk 32
            versionCode 1
            versionName “1.0”
    
            testInstrumentationRunner “androidx.test.runner.AndroidJUnitRunner”
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile(‘proguard-android-optimize.txt’), ‘proguard-rules.pro’
            }
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
        buildFeatures {
            viewBinding true
        }
    }
    
    dependencies {
    
        implementation ‘androidx.appcompat:appcompat:1.5.1’
        implementation ‘com.google.android.material:material:1.7.0’
        implementation ‘androidx.constraintlayout:constraintlayout:2.1.4’
        implementation ‘androidx.navigation:navigation-fragment:2.5.3’
        implementation ‘androidx.navigation:navigation-ui:2.5.3’
        implementation ‘com.google.firebase:firebase-messaging:23.1.1’
        testImplementation ‘junit:junit:4.13.2’
        androidTestImplementation ‘androidx.test.ext:junit:1.1.4’
        androidTestImplementation ‘androidx.test.espresso:espresso-core:3.5.0’
        implementation platform(‘com.google.firebase:firebase-bom:31.1.1’)
        implementation ‘com.google.firebase:firebase-analytics’
        implementation ‘com.android.volley:volley:1.2.1’
    }
    
    apply plugin: ‘com.google.gms.google-services’

              

Agregamos código del service en la App

Agregamos el Service en el AndroidManifest.xml:

singleton

Código:


<?xml version=”1.0″ encoding=”utf-8″?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
    xmlns:tools=”http://schemas.android.com/tools”>

    <application
        android:allowBackup=”true”
        android:dataExtractionRules=”@xml/data_extraction_rules”
        android:fullBackupContent=”@xml/backup_rules”
        android:icon=”@mipmap/ic_launcher”
        android:label=”@string/app_name”
        android:roundIcon=”@mipmap/ic_launcher_round”
        android:supportsRtl=”true”
        android:theme=”@style/Theme.AppNotificaciones”
        tools:targetApi=”31″>
        <!– 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/icononotificacion” />
            <activity
            android:name=”.MainActivity”
            android:exported=”true”
            android:label=”@string/app_name”
            android:theme=”@style/Theme.AppNotificaciones.NoActionBar”>
            <intent-filter>
            <action android:name=”android.intent.action.MAIN” />

            <category android:name=”android.intent.category.LAUNCHER” />
            </intent-filter>

            <meta-data
                android:name=”android.app.lib_name”
                android:value=”” />
                </activity>

                <service
            android:name=”.MyFirebaseMessagingService”
            android:exported=”false”>
            <intent-filter>
            <action android:name=”com.google.firebase.MESSAGING_EVENT” />
            </intent-filter>
            </service>

            </application>

            </manifest>
              

Creamos la clase MyFirebaseMessaging, que hereda de FirebaseMessaging, para atender las notificaciones push, acá podemos customizar todo lo referido a notificaciones, y aca va a depender del tipo de notificación que enviemos, ya sea de tipo Notification o de tipo Data.

singleton

Agregamos ícono de notificación

Vamos a agregar el icono de la notificación, el icono va a tener color blanco a menos que lo cambiemos.

Voy a buscar la página Android Asset Studio: https://romannurik.github.io/AndroidAssetStudio/

singleton

En este sitio vamos a poder generar el icono, tiene muchos tipos, le pongo un nombre y lo descargo. Aparecen todas las carpetas donde está el icono con los distintos formatos. Se agrega en la carpeta res y listo.

Modifico el AndroidManifest.xml para acomodar el color del ícono.

singleton

En el AndroidManifest, lo paso sin el color:

singleton

Registramos dispositivo

singleton

Agregamos el código para registrar el dispositivo, esto es lo que nos va a dar el token, el device ID.

En la MainActivity, al iniciar la app, revisa si el código recibido es distinto al último que tenía, lo envía al servidor, y una vez que devuelve el Ok lo guardo en la App. Si no hubo cambios en el token no hago nada.

El registro se hace una sola vez, por más que invoque varias veces el método registrar, va a devolver siempre el mismo token. A lo largo del tiempo puede cambiar, por eso lo hacemos así.

singleton

El código completo del registro en MainActivity:


    //class MainActivity….

    @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
  
          //Invocacion al metodo que registra el dispositivo
          registrarDispositivo();
      }
  
  
      private void registrarDispositivo(){
          FirebaseMessaging.getInstance().getToken()
                  .addOnCompleteListener(new OnCompleteListener() {
                      @Override
                      public void onComplete(@NonNull Task task) {
                          if (!task.isSuccessful()) {
                              Log.w(ContentValues.TAG,
                                      “Fetching FCM registration token failed”,
                                      task.getException());
                              return;
                          }
  
                          String token = task.getResult();
                          String tokenGuardado = getSharedPreferences(Constantes.SP_FILE,0)
                                  .getString(Constantes.SP_KEY_DEVICEID,null);
                          if (token != null ){
                              if (tokenGuardado == null || !token.equals(tokenGuardado)){
                                  //registramos el token en el servidor
                                  DeviceManager.postRegistrarDispositivoEnServidor( token, MainActivity.this);
                              }
                          }
  
                          Toast.makeText(MainActivity.this, token,
                                  Toast.LENGTH_SHORT).show();
                      }
                  });
      }

              

Y la clase que hace el post al servicio de registro de dispositivo (agregamos la librería Volley y hacemos el post):

singleton
singleton

El código completo del envío:


    public class DeviceManager {
        public static void postRegistrarDispositivoEnServidor(String token, Context context){
            // Instantiate the RequestQueue.
            RequestQueue queue = Volley.newRequestQueue(context);
            String url = Configuracion.URL_SERVIDOR;
    
            // Request a string response from the provided URL.
            StringRequest stringRequest = new StringRequest(Request.Method.POST, url,
                    new Response.Listener() {
                        @Override
                        public void onResponse(String response) {
                            try {
                                JSONObject respObj = new JSONObject(response);
    
                                String code = respObj.getString(“code”);
                                String message = respObj.getString(“message”);
                                Integer id = respObj.getInt(“id”);
    
                                if (“OK”.equals(code)){
                                    context.getSharedPreferences(Constantes.SP_FILE,0).edit()
                                            .putString(Constantes.SP_KEY_DEVICEID, token).commit();
                                    if (id!=0){
                                        context.getSharedPreferences(Constantes.SP_FILE,0)
                                                .edit().putInt(Constantes.SP_KEY_ID,id).commit();
                                    }
                                }
                            } catch (JSONException e) {
                                e.printStackTrace();
                            }
                        }
                    }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    //textView.setText(“That didn’t work!”);
                    Toast.makeText(context, “Error registrando token en servidor:”
                            + error.getMessage(), Toast.LENGTH_SHORT).show();
                }
            }) {
                @Override
                protected Map getParams() {
                    Map params = new HashMap();
    
                    params.put(“DEVICEID”, token);
                    if (context.getSharedPreferences(Constantes.SP_FILE,0)
                            .getInt(Constantes.SP_KEY_ID,0) != 0){
                        Integer val = context.getSharedPreferences(Constantes.SP_FILE,0)
                                .getInt(Constantes.SP_KEY_ID,0);
                        params.put(“ID”, val.toString());
                    }
                    return params;
                };
    
            };
    
            // Add the request to the RequestQueue.
            queue.add(stringRequest);
    
        }
    }
              
              

Si queremos probar si llegan las notificaciones push también podemos hacerlo desde Firebase, la parte de messaging, creamos una campaña e ingresamos los datos que queremos que se envíen. 

singleton

Guardamos deviceID en Servidor

singleton

Ahora falta implementar la conexión con el servidor, que reciba el token y lo guarde.

Dentro de Android vamos a usar la librería volley para hacer el request, el post al servidor. Voy a implementar un llamado genérico, y después le agrego los parámetros y la URL final porque todavía no la tengo.

singleton

Implementamos el Servidor (Backend)

El servidor es muy simple, vamos a tener solamente dos servicios: registrar y notificar. Va a estar implementado en PHP porque es muy rápido para hacer y la mayoría de los hostings lo soportan.

Vamos a almacenar los devicesID, los ID de los dispositivos, en una base de datos MySQL.

Este es el código para crear la base de datos:


  CREATE TABLE `DISPOSITIVOS` (
    `ID` int(11) NOT NULL,
    `DEVICEID` varchar(400) NOT NULL
  ) ;

  ALTER TABLE `DISPOSITIVOS`
    ADD PRIMARY KEY (`ID`);

  ALTER TABLE `DISPOSITIVOS`
    MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
              
              

La tabla dispositivos, solo necesito guardar el deviceID, esta tabla tiene una PK auto incremental y un varchar (400) para el deviceID.

Si la app  tuviera registro de usuarios, podríamos vincular el device ID a esa tabla, al usuario, porque lo que interesa saber es de quién es ese dispositivo, para poder notificarlo a ese usuario.

Creo los dos archivos de servicio: registrarDispositivo.php y enviarNotificacion.php.

Creó también un archivo de configuración para guardar los datos, en este caso van a ser los de la base.


    <?php

    //Parametros conexion DB
    define (‘DB_HOST’, “”);
    define (‘DB_USER’, “”);
    define (‘DB_PASSWORD’, “”);
    define (‘DB_DATABASE’, “”);
    
    //ApiKey de FCM
    define (‘FCM_APIKEY’, “”);
    
    ?>
              

Creó también un archivo con los headers, para que se pueda acceder al rest.


    <?php
    header(“Access-Control-Allow-Origin: *”);
    header(“Content-Type: application/json; charset=UTF-8”);
    header(“Access-Control-Allow-Methods: GET,POST”);
    header(“Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With”);
    ?>
              

El servicio registrar dispositivo recibe el ID de dispositivo y el ID de usuario (el de la tabla de dispositivos) y retorna el ID de registro.


    <?php
    include_once ‘headers.php’;
    include_once ‘configuracion.php’;
    
    class RegistroResultado {
        public $code = “”;
        public $message = “”;
        public $id = 0;
    }
    
    $response = new RegistroResultado;
    $response->code = “OK”;
    $response->message = “”;
    $response->id = 0;
    try{
    
          //***Verificacion de campos
          if (isset($_POST[‘DEVICEID’])) {
            $deviceid = $_POST[‘DEVICEID’];
    
            if($deviceid==NULL) {
                $response->code = “ERR”;
                $response->message = “Token Nulo”;
                $response->id = 0;
            }else{
                $id = null;
                if (isset($_POST[‘ID’])) {
                    $id = $_POST[‘ID’];
                }
                //registro en la DB el token y el estado
                $mysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE);
                
                if ($id != null){
                    $stmt = $mysqli->prepare(“UPDATE DISPOSITIVOS SET DEVICEID = ? WHERE ID =  ?”);
                    $stmt->bind_param(“si”, $deviceid, $id);
                    $resultado1 = $stmt->execute();
                    $stmt->close();
                }else{
                    $stmt = $mysqli->prepare(“INSERT INTO DISPOSITIVOS (DEVICEID) VALUES (?)”);
                    $stmt->bind_param(“s”, $deviceid);
                    $resultado1 = $stmt->execute();
                    $response->id = $mysqli->insert_id;
                    $stmt->close();
                }
                $mysqli -> close();
            }
          }else{
            $response->code = “ERR”;
            $response->message = “Faltan campos”;
            $response->id = 0;
        }
    
    } catch(Exception $e1) {
        $response->code = “ERR”;
        $response->message = “Error”;
        $response->id = 0;
    }
    
    $myJSON = json_encode($response);
    echo $myJSON;
    ?>                
              

Si viene el ID de usuario, voy a actualizar la tabla el device ID, si no viene lo inserto. 

Deberíamos agregar algún método de seguridad al servidor, algún token de autenticación, pero escapa al objetivo del tutorial. 

Enviamos Notificación

singleton

Ahora lo que nos falta es el servicio de enviar notificaciones push en el servidor, hay dos formas de enviar notificaciones para FCM API V1 y API Heredada.

La API V1 es la que tiene Oauth 2.0, es la más segura, pero por el momento hay que agregar una librería de FCM en el servidor. Como no hay librerías oficiales para php vamos a usar la Heredada.

singleton

Habilito la opción de API KEY (Clave API) Heredada en FCM, acá se pueden poner restricciones de seguridad, por ejemplo si quiero que solamente responda a la IP del servidor, con esto nadie más puede acceder.

singleton

Guardo la API KEY para poder conectarme, es el identificador y mecanismo de seguridad.

singleton

Ahora el servicio de enviar notificaciones, este servicio va a enviar el mensaje, recibe el ID del usuario, el título y el mensaje del texto enviar. Para ese usuario, va a buscar el device ID a la tabla de dispositivos (para decirle a FCM a quién se lo tiene que mandar) y arma el post con los datos que le va a enviar a FCM: el título y el mensaje, usando la API KEY, y el deviceID.

FCM recibe los parámetros que vamos a mandar en Notification, básicamente le decimos mandale este título y este mensaje a este dispositivo.

singleton

El archivo completo:


    <?php 
    include_once ‘headers.php’;
    include_once ‘configuracion.php’;
    
    class EnvioNotificacionResultado {
        public $code = “”;
        public $message = “”;
    }
    
    $response = new EnvioNotificacionResultado;
    $response->code = “OK”;
    $response->message = “Enviado correctamente”;
    
    try{
          //***Verificacion de campos
          if (isset($_POST[‘ID’]) && isset($_POST[‘TITULO’]) 
            && isset($_POST[‘MENSAJE’])) {
            $id = $_POST[‘ID’];
            $titulo = $_POST[‘TITULO’];
            $mensaje = $_POST[‘MENSAJE’];
    
            if($id==NULL || $titulo==NULL || $mensaje==NULL) {
                $response->code = “ERR”;
                $response->message = “Faltan parametro”;
            }else{
                $registatoin_ids = array();
                $datos = array();
                //busco en la DB el id
                $mysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE);
    
                $stmt = mysqli_prepare($mysqli, “select DEVICEID from DISPOSITIVOS WHERE ID = ?”);
                mysqli_stmt_bind_param($stmt, “i”, $id);
                mysqli_stmt_execute($stmt);
                if ($resultado = mysqli_stmt_get_result($stmt)) {
                    while ($fila = mysqli_fetch_assoc($resultado)){
                        if ($fila[“DEVICEID”]!= null){
                            $registatoin_ids[] = $fila[“DEVICEID”];
    
                        }
                    }
                    $stmt->close();
                }
                $mysqli->close();
                $notification= array();	
                $notification[“title”] = $titulo;
                $notification[“body”] = $mensaje;
    
                $fields = array(
                    ‘registration_ids’ => $registatoin_ids,
                    ‘notification’ => $notification,
                    //’data’ => $datos,
                    ‘direct_book_ok’ => true
                );
    
    
                $url = ‘https://fcm.googleapis.com/fcm/send’;
                // Your Firebase Server API Key
                $headers = array( “authorization: key=”.FCM_APIKEY.””
                ,”content-type: application/json”);
    
                // Open curl 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);
    
                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));
                }
                curl_close($ch);
            }
        }else{
            $response->code = “ERR”;
            $response->message = “Faltan campos”;
        }
    
    } catch(Exception $e1) {
        $response->code = “ERR”;
        $response->message = “Error”;
    }
        
    $myJSON = json_encode($response);
    echo $myJSON;
    ?>              
              

Código completo App y Rest para descargar

En el siguiente el link de github podés encontrar el código completo:

https://github.com/unsimpledev/ProyectoNotificaciones