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.
Vamos a usar Firebase Cloud Messaging (FCM), el servicio gratuito de notificaciones de Firebase.
La arquitectura FCM tiene tres patas:

La secuencia consta de 3 pasos, bien definidos, que voy a explicar a continuación:
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.

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”

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é!”.

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.
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.

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.

Descargamos el archivo y lo ubicamos en la carpeta 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:

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
}


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 el Service en el AndroidManifest.xml:

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.

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/

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.

En el AndroidManifest, lo paso sin el color:


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í.

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):


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.


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.

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.

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.

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.

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

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.

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;
?>
En el siguiente el link de github podés encontrar el código completo:
https://github.com/unsimpledev/ProyectoNotificaciones