Notifiche Push con Firebase Cloud Messaging (FCM) in Android Java: Guida Completa

Firebase Cloud Messaging Android notifiche push

Guida completa a Firebase Cloud Messaging (FCM) per Android (Java)

Firebase Cloud Messaging (FCM) è il servizio di messaggistica cloud di Google che permette di inviare notifiche push e messaggi dati a dispositivi Android, iOS e al web. In questo tutorial pratico, quindi, vedremo passo passo come integrare FCM in un’app Android scritta in Java, dalla configurazione iniziale fino all’invio di notifiche da console e da server.

1. Perché usare Firebase Cloud Messaging Android

FCM è una soluzione scalabile, affidabile e gratuita per:

  • In primo luogo, inviare notifiche push personalizzate agli utenti.
  • Inoltre, gestire campagne di engagement (promozioni, reminder, aggiornamenti).
  • In particolare, inviare messaggi silenziosi (solo dati) per sincronizzare contenuti in background.
  • Allo stesso modo, raggiungere singoli dispositivi, gruppi di dispositivi o interi segmenti tramite topic.

L’integrazione con il resto dell’ecosistema Firebase (Analytics, Remote Config, A/B Testing), pertanto, lo rende uno strumento potente per gestire le comunicazioni della tua app.

2. Configurazione Firebase Cloud Messaging Android

2.1 Creare il progetto Firebase per Android

1. In primo luogo, vai su Firebase Console e crea un nuovo progetto.
2. In seguito, aggiungi un’app Android al progetto inserendo il package name esatto della tua app (es: com.example.myapp).
3. Infine, scarica il file google-services.json e posizionalo nella cartella app/ del tuo progetto Android (a livello del file app/build.gradle).

2.2 Aggiornare i file Gradle per FCM Android

Nel file build.gradle a livello di progetto (Project), in primo luogo, configura le dipendenze richieste da Firebase.

buildscript {
    dependencies {
        // Plugin Google Services
        classpath 'com.google.gms:google-services:4.4.2'
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

Nel file app/build.gradle del modulo app, invece, aggiungi le dipendenze specifiche per FCM e per i servizi di Google Play.

plugins {
    id 'com.android.application'
    id 'com.google.gms.google-services' // plugin Google Services
}

android {
    namespace 'com.example.myapp'
    compileSdk 34

    defaultConfig {
        applicationId 'com.example.myapp'
        minSdk 21
        targetSdk 34
        versionCode 1
        versionName '1.0'
    }
}

dependencies {
    // Firebase BOM per gestire in modo allineato le versioni
    implementation platform('com.google.firebase:firebase-bom:33.2.0')

    // Messaging
    implementation 'com.google.firebase:firebase-messaging'
}

Infine, sincronizza il progetto con Gradle, in modo che tutte le modifiche vengano applicate correttamente.

3. Implementazione di FirebaseMessagingService in Android

Per ricevere i messaggi FCM quando l’app è in foreground (aperta) o in background (ma ancora in memoria), bisogna creare una classe che estende FirebaseMessagingService; in questo modo, infatti, potrai intercettare e gestire i messaggi in arrivo.

3.1 Creare la classe MyFirebaseMessagingService per FCM Android

package com.example.myapp;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.util.Log;

import androidx.core.app.NotificationCompat;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

public class MyFirebaseMessagingService extends FirebaseMessagingService {

    private static final String TAG = "MyFirebaseMsgService";
    private static final String CHANNEL_ID = "fcm_default_channel";

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);

        // Log di debug
        Log.d(TAG, "From: " + remoteMessage.getFrom());

        // Controlla se il messaggio contiene un payload dati
        if (remoteMessage.getData().size() > 0) {
            Log.d(TAG, "Message data payload: " + remoteMessage.getData());
        }

        // Controlla se il messaggio contiene un payload notifica
        if (remoteMessage.getNotification() != null) {
            Log.d(TAG, "Message Notification Body: "
                    + remoteMessage.getNotification().getBody());
        }

        // In questo esempio mostriamo sempre una notifica locale personalizzata
        String title = remoteMessage.getNotification() != null
                ? remoteMessage.getNotification().getTitle()
                : "Nuovo messaggio";
        String body = remoteMessage.getNotification() != null
                ? remoteMessage.getNotification().getBody()
                : remoteMessage.getData().get("body");

        sendNotification(title, body);
    }

    // Metodo per mostrare una notifica locale (vedi sezione 5 per dettagli)
    private void sendNotification(String title, String messageBody) {
        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(
                this,
                0,
                intent,
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
                        ? PendingIntent.FLAG_IMMUTABLE
                        : PendingIntent.FLAG_ONE_SHOT
        );

        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder =
                new NotificationCompat.Builder(this, CHANNEL_ID)
                        .setSmallIcon(R.drawable.ic_notification)
                        .setContentTitle(title)
                        .setContentText(messageBody)
                        .setAutoCancel(true)
                        .setSound(defaultSoundUri)
                        .setContentIntent(pendingIntent);

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

        // Canale di notifica per Android 8.0+
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            CharSequence channelName = "Notifiche generali";
            String channelDescription = "Canale predefinito per le notifiche FCM";
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(
                    CHANNEL_ID,
                    channelName,
                    importance
            );
            channel.setDescription(channelDescription);
            notificationManager.createNotificationChannel(channel);
        }

        notificationManager.notify(0, notificationBuilder.build());
    }
}

3.2 Dichiarare il servizio nel Manifest

Nel file AndroidManifest.xml, in primo luogo, aggiungi il servizio e i permessi internet, se non presenti:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApp">

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

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>

4. Gestione del token FCM Android con onNewToken

Ogni dispositivo ha un token FCM univoco che identifica l’istanza dell’app. Questo token può cambiare, quindi è importante gestire l’evento di rinnovo tramite onNewToken e, di conseguenza, aggiornare il token salvato sul tuo server.

@Override
public void onNewToken(String token) {
    super.onNewToken(token);
    Log.d(TAG, "Refreshed token: " + token);

    // Invia il token al tuo server per associarlo all'utente
    sendRegistrationToServer(token);
}

private void sendRegistrationToServer(String token) {
    // TODO: invia il token al tuo backend tramite API REST
    // Ad esempio con Retrofit o HttpUrlConnection
}

Puoi anche recuperare manualmente il token (ad esempio in MainActivity); in questo modo, per esempio, puoi mostrarlo nei log o inviarlo immediatamente al tuo backend.

FirebaseMessaging.getInstance().getToken()
        .addOnCompleteListener(task -> {
            if (!task.isSuccessful()) {
                Log.w(TAG, "Fetching FCM registration token failed", task.getException());
                return;
            }

            String token = task.getResult();
            Log.d(TAG, "Current token: " + token);
        });

5. Mostrare una notifica locale FCM Android

Nell’esempio della sezione 3 abbiamo già implementato il metodo sendNotification. Di seguito, quindi, lo riportiamo separatamente con commenti dettagliati, in modo da chiarire ogni singolo passaggio.

private void sendNotification(String title, String messageBody) {
    // Intent per aprire l'attività principale quando l'utente tocca la notifica
    Intent intent = new Intent(this, MainActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

    // PendingIntent richiesto per le notifiche
    PendingIntent pendingIntent = PendingIntent.getActivity(
            this,
            0,
            intent,
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
                    ? PendingIntent.FLAG_IMMUTABLE
                    : PendingIntent.FLAG_ONE_SHOT
    );

    // Suono di default per la notifica
    Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);

    // Creazione della notifica
    NotificationCompat.Builder notificationBuilder =
            new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setSmallIcon(R.drawable.ic_notification) // Icona piccola obbligatoria
                    .setContentTitle(title)
                    .setContentText(messageBody)
                    .setAutoCancel(true) // Chiudi la notifica quando viene toccata
                    .setSound(defaultSoundUri)
                    .setContentIntent(pendingIntent);

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

    // Per Android 8.0+ è obbligatorio il NotificationChannel
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        CharSequence channelName = "Notifiche generali";
        String channelDescription = "Canale predefinito per le notifiche FCM";
        int importance = NotificationManager.IMPORTANCE_DEFAULT;
        NotificationChannel channel = new NotificationChannel(
                CHANNEL_ID,
                channelName,
                importance
        );
        channel.setDescription(channelDescription);
        notificationManager.createNotificationChannel(channel);
    }

    // Mostra la notifica (ID 0, puoi usare ID diversi per notifiche multiple)
    notificationManager.notify(0, notificationBuilder.build());
}

6. Notifiche FCM Android: Differenza tra Foreground e Background

Il comportamento dei messaggi FCM, in altre parole, dipende dallo stato dell’app e dal tipo di payload:

  • App in foreground:
    • In questo caso, i messaggi con payload dati (solo data) vengono consegnati a onMessageReceived.
    • Inoltre, i messaggi con payload notifica + dati arrivano comunque a onMessageReceived (su Android) e puoi gestirli per mostrare una notifica personalizzata.
  • App in background o chiusa:
    • In questo scenario, i messaggi con payload notifica sono gestiti automaticamente da FCM: Android mostra la notifica usando il titolo e il corpo definiti nel messaggio, mentre onMessageReceived potrebbe non essere chiamato in tutti i casi.
    • Invece, i messaggi con solo payload dati vengono recapitati a onMessageReceived (dipende dalle condizioni di sistema e di rete) e non viene mostrata automaticamente alcuna notifica: di conseguenza, devi mostrarla tu.

Per un controllo completo del comportamento, pertanto, è consigliabile usare principalmente messaggi dati e costruire manualmente le notifiche nell’app.

7. Invio di messaggi dalla Firebase Console

La Firebase Console, in altre parole, è il modo più semplice per iniziare a testare FCM:

  • In primo luogo, vai su Firebase Console > Cloud Messaging.
  • In seguito, clicca su Invia il tuo primo messaggio (o “Nuovo messaggio”).
  • Quindi, inserisci titolo e testo della notifica.
  • In Target, invece, scegli l’app Android di destinazione.
  • In Schedulazione puoi inviare subito o, in alternativa, pianificare.
  • In Opzioni avanzate, inoltre, puoi aggiungere una chiave dati personalizzata (ad esempio screen=promo).
  • Infine, clicca su Invia.

I messaggi inviati da console usano di default un payload notifica. Se vuoi testare payload dati puri, invece, è meglio usare le API server o strumenti come Postman.

8. Invio Messaggi FCM Android da Server (HTTP API)

Per inviare notifiche da un server puoi usare l’HTTP v1 API di FCM; in questo modo, per esempio, puoi integrare l’invio di messaggi in un backend esistente o in un sistema di automazione.

8.1 Ottenere le credenziali server per FCM Android

1. In Firebase Console, in primo luogo, vai su Impostazioni progetto > Account di servizio.
2. In seguito, clicca su Genera nuova chiave privata per ottenere un JSON di credenziali.
3. Infine, usa questa chiave nel tuo backend per generare un token OAuth 2.0 o per configurare l’SDK Admin di Firebase.

8.2 Esempio di richiesta HTTP v1 (JSON)

URL di destinazione (sostituisci PROJECT_ID con l’ID del tuo progetto); in questo modo, la richiesta raggiungerà correttamente l’istanza FCM associata:

POST https://fcm.googleapis.com/v1/projects/PROJECT_ID/messages:send
Authorization: Bearer <ACCESS_TOKEN>
Content-Type: application/json

Body di esempio per inviare a un singolo token; in particolare, questo payload mostra come combinare campi notifica e dati:

{
  "message": {
    "token": "DEVICE_FCM_TOKEN",
    "notification": {
      "title": "Offerta speciale",
      "body": "Sconto del 20% solo per oggi!"
    },
    "data": {
      "screen": "offers",
      "discount": "20"
    }
  }
}

Nel tuo server dovrai generare <ACCESS_TOKEN> usando le credenziali del service account; a tal fine, puoi seguire la documentazione ufficiale di FCM Admin SDK per il linguaggio che preferisci.

9. Invio a topic (subscribe / unsubscribe)

I topic permettono di inviare messaggi a gruppi logici di dispositivi (ad esempio tutti gli utenti interessati alle notizie sportive). I client, in questo modo, si iscrivono ai topic direttamente dall’app.

Articoli correlati

9.1 Iscrizione e disiscrizione ai topic in Java

import com.google.firebase.messaging.FirebaseMessaging;

// Iscrizione a un topic
FirebaseMessaging.getInstance().subscribeToTopic("news")
        .addOnCompleteListener(task -> {
            if (task.isSuccessful()) {
                Log.d(TAG, "Iscritto al topic news");
            } else {
                Log.w(TAG, "Errore iscrizione topic news", task.getException());
            }
        });

// Disiscrizione da un topic
FirebaseMessaging.getInstance().unsubscribeFromTopic("news")
        .addOnCompleteListener(task -> {
            if (task.isSuccessful()) {
                Log.d(TAG, "Disiscritto dal topic news");
            } else {
                Log.w(TAG, "Errore disiscrizione topic news", task.getException());
            }
        });

9.2 Invio di un messaggio a un topic da server

{
  "message": {
    "topic": "news",
    "notification": {
      "title": "Ultime notizie",
      "body": "Leggi ora gli aggiornamenti in tempo reale."
    },
    "data": {
      "category": "sport"
    }
  }
}

10. Payload dati vs payload notifica in Firebase Cloud Messaging Android

FCM supporta due tipi principali di payload, vale a dire:

  • Payload notifica (notification):
    • Contiene campi come title, body, icon; in questo modo, il sistema può costruire direttamente la notifica.
    • È gestito automaticamente dal sistema quando l’app è in background; di conseguenza, non devi creare manualmente la notifica.
    • È più semplice da usare ma, tuttavia, è meno flessibile.
  • Payload dati (data):
    • Contiene coppie chiave/valore definite da te (tutte stringhe) e, in particolare, consente di modellare il contenuto in base alle esigenze della tua app.
    • È sempre consegnato a onMessageReceived (nei limiti delle condizioni di sistema) e, di conseguenza, ti permette di eseguire logica personalizzata.
    • Ti permette di decidere come e quando mostrare notifiche e cosa fare al tap; in questo modo, ottieni il massimo controllo sull’esperienza utente.

10.1 Esempio solo notifiche

{
  "message": {
    "token": "DEVICE_FCM_TOKEN",
    "notification": {
      "title": "Benvenuto",
      "body": "Grazie per aver installato la nostra app!"
    }
  }
}

10.2 Esempio solo dati

{
  "message": {
    "token": "DEVICE_FCM_TOKEN",
    "data": {
      "type": "sync",
      "entity": "messages",
      "forceRefresh": "true"
    }
  }
}

Nel caso “solo dati”, sarai tu, in onMessageReceived, a decidere se mostrare una notifica e con quale contenuto; in conclusione, l’intero comportamento sarà definito dalla logica della tua applicazione.

11. Esempio completo: MainActivity in Java

Per concludere, ecco un esempio di MainActivity minimale in Java che inizializza Firebase e mostra il token corrente nei log, in modo che tu possa verificare facilmente il corretto funzionamento.

package com.example.myapp;

import android.os.Bundle;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.google.firebase.FirebaseApp;
import com.google.firebase.messaging.FirebaseMessaging;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Inizializza Firebase (in genere è automatico, ma puoi forzarlo se necessario)
        FirebaseApp.initializeApp(this);

        // Recupera il token FCM corrente
        FirebaseMessaging.getInstance().getToken()
                .addOnCompleteListener(task -> {
                    if (!task.isSuccessful()) {
                        Log.w(TAG, "Fetching FCM registration token failed", task.getException());
                        return;
                    }

                    String token = task.getResult();
                    Log.d(TAG, "FCM token: " + token);
                });

        // Esempio: iscrizione a un topic
        FirebaseMessaging.getInstance().subscribeToTopic("news")
                .addOnCompleteListener(task -> {
                    if (task.isSuccessful()) {
                        Log.d(TAG, "Iscritto al topic news");
                    } else {
                        Log.w(TAG, "Errore iscrizione topic news", task.getException());
                    }
                });
    }
}

Con questa configurazione, in conclusione, hai una base completa per lavorare con Firebase Cloud Messaging in Android usando Java: puoi ricevere messaggi, gestire token, mostrare notifiche personalizzate, usare topic e integrare un backend per l’invio di campagne push avanzate.

Commenti

Rispondi

Scopri di più da App Tutorial

Abbonati ora per continuare a leggere e avere accesso all'archivio completo.

Continua a leggere