To target a message to multiple devices, use Topic messaging. This feature allows you to send a message to multiple devices that have opted in to a particular topic.
This tutorial focuses on sending topic messages from your app server using the Admin SDK or REST API for FCM, and receiving and handling them in an android app. We'll cover message handling for both backgrounded and foregrounded apps. All the steps to achieve this are covered, from setup to verification.
Set up the SDK
This section may cover steps you already completed if you have set up an Android client app for FCM or worked through the steps to Send your First Message.
Before you begin
Install or update Android Studio to its latest version.
Make sure that your project meets these requirements (note that some products might have stricter requirements):
- Targets API level 19 (KitKat) or higher
- Uses Android 4.4 or higher
- Uses
Jetpack (AndroidX),
which includes meeting these version requirements:
com.android.tools.build:gradle
v7.3.0 or latercompileSdkVersion
28 or later
Set up a physical device or use an emulator to run your app.
Note that Firebase SDKs with a dependency on Google Play services require the device or emulator to have Google Play services installed.Sign into Firebase using your Google account.
If you don't already have an Android project and just want to try out a Firebase product, you can download one of our quickstart samples.
Create a Firebase project
Before you can add Firebase to your Android app, you need to create a Firebase project to connect to your Android app. Visit Understand Firebase Projects to learn more about Firebase projects.
Register your app with Firebase
To use Firebase in your Android app, you need to register your app with your Firebase project. Registering your app is often called "adding" your app to your project.
Go to the Firebase console.
In the center of the project overview page, click the Android icon (
) or Add app to launch the setup workflow.Enter your app's package name in the Android package name field.
A package name uniquely identifies your app on the device and in the Google Play Store.
A package name is often referred to as an application ID.
Find your app's package name in your module (app-level) Gradle file, usually
app/build.gradle
(example package name:com.yourcompany.yourproject
).Be aware that the package name value is case-sensitive, and it cannot be changed for this Firebase Android app after it's registered with your Firebase project.
(Optional) Enter other app information: App nickname and Debug signing certificate SHA-1.
App nickname: An internal, convenience identifier that is only visible to you in the Firebase console
Debug signing certificate SHA-1: A SHA-1 hash is required by Firebase Authentication (when using Google Sign In or phone number sign in) and Firebase Dynamic Links.
Click Register app.
Add a Firebase configuration file
Download and then add the Firebase Android configuration file (
) to your app:google-services.json Click Download google-services.json to obtain your Firebase Android config file.
Move your config file into the module (app-level) root directory of your app.
The Firebase config file contains unique, but non-secret identifiers for your project. To learn more about this config file, visit Understand Firebase Projects.
You can download your Firebase config file again at any time.
Make sure the config file name is not appended with additional characters, like
(2)
.
To make the values in your
config file accessible to Firebase SDKs, you need the Google services Gradle plugin (google-services.json google-services
).In your root-level (project-level) Gradle file (
<project>/build.gradle.kts
or<project>/build.gradle
), add the Google services plugin as a dependency:Kotlin
plugins { id("com.android.application") version "7.3.0" apply false // ... // Add the dependency for the Google services Gradle plugin id("com.google.gms.google-services") version "4.4.2" apply false }
Groovy
plugins { id 'com.android.application' version '7.3.0' apply false // ... // Add the dependency for the Google services Gradle plugin id 'com.google.gms.google-services' version '4.4.2' apply false }
In your module (app-level) Gradle file (usually
<project>/<app-module>/build.gradle.kts
or<project>/<app-module>/build.gradle
), add the Google services plugin:Kotlin
plugins { id("com.android.application") // Add the Google services Gradle plugin id("com.google.gms.google-services") // ... }
Groovy
plugins { id 'com.android.application' // Add the Google services Gradle plugin id 'com.google.gms.google-services' // ... }
Add Firebase SDKs to your app
In your module (app-level) Gradle file (usually
<project>/<app-module>/build.gradle.kts
or<project>/<app-module>/build.gradle
), add the dependency for the Firebase Cloud Messaging library for Android. We recommend using the Firebase Android BoM to control library versioning.For an optimal experience with Firebase Cloud Messaging, we recommend enabling Google Analytics in your Firebase project and adding the Firebase SDK for Google Analytics to your app.
dependencies { // Import the BoM for the Firebase platform implementation(platform("com.google.firebase:firebase-bom:33.4.0")) // Add the dependencies for the Firebase Cloud Messaging and Analytics libraries // When using the BoM, you don't specify versions in Firebase library dependencies implementation("com.google.firebase:firebase-messaging") implementation("com.google.firebase:firebase-analytics") }
By using the Firebase Android BoM, your app will always use compatible versions of Firebase Android libraries.
(Alternative) Add Firebase library dependencies without using the BoM
If you choose not to use the Firebase BoM, you must specify each Firebase library version in its dependency line.
Note that if you use multiple Firebase libraries in your app, we strongly recommend using the BoM to manage library versions, which ensures that all versions are compatible.
dependencies { // Add the dependencies for the Firebase Cloud Messaging and Analytics libraries // When NOT using the BoM, you must specify versions in Firebase library dependencies implementation("com.google.firebase:firebase-messaging:24.0.2") implementation("com.google.firebase:firebase-analytics:22.1.2") }
Sync your Android project with Gradle files.
Gradle builds that use Android Gradle plugin (AGP) v4.2 or earlier need to enable Java 8 support. Otherwise, these Android projects get a build failure when adding a Firebase SDK.
To fix this build failure, you can follow one of two options:
- Add the listed
compileOptions
from the error message to your app-levelbuild.gradle.kts
orbuild.gradle
file. - Increase the
minSdk
for your Android project to 26 or above.
Learn more about this build failure in this FAQ.
- Add the listed
Subscribe the client app to a topic
Client apps can subscribe to any existing topic, or they can create a new topic. When a client app subscribes to a new topic name (one that does not already exist for your Firebase project), a new topic of that name is created in FCM and any client can subsequently subscribe to it.
To subscribe to a topic, the client app calls Firebase Cloud Messaging
subscribeToTopic()
with the FCM topic name. This method
returns a Task
, which can be used by a completion listener to determine whether
the subscription succeeded:
Kotlin+KTX
Firebase.messaging.subscribeToTopic("weather") .addOnCompleteListener { task -> var msg = "Subscribed" if (!task.isSuccessful) { msg = "Subscribe failed" } Log.d(TAG, msg) Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() }
Java
FirebaseMessaging.getInstance().subscribeToTopic("weather") .addOnCompleteListener(new OnCompleteListener<Void>() { @Override public void onComplete(@NonNull Task<Void> task) { String msg = "Subscribed"; if (!task.isSuccessful()) { msg = "Subscribe failed"; } Log.d(TAG, msg); Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show(); } });
To unsubscribe, the client app calls Firebase Cloud Messaging unsubscribeFromTopic()
with the topic name.
Receive and handle topic messages
FCM delivers topic messages in the same way as other downstream messages.
To receive messages, use a service that extends
FirebaseMessagingService
.
Your service should override the onMessageReceived
and onDeletedMessages
callbacks.
The time window for handling a message may be shorter than 20 seconds depending on delays
incurred ahead of calling onMessageReceived
, including OS delays, app startup time,
the main thread being blocked by other operations, or previous onMessageReceived
calls taking too long. After that time, various OS behaviors such as Android's
process
killing or Android O's
background execution limits may interfere with your ability to complete your work.
onMessageReceived
is provided for most message types, with the following
exceptions:
-
Notification messages delivered when your app is in the background. In this case, the notification is delivered to the device’s system tray. A user tap on a notification opens the app launcher by default.
-
Messages with both notification and data payload, when received in the background. In this case, the notification is delivered to the device’s system tray, and the data payload is delivered in the extras of the intent of your launcher Activity.
In summary:
App state | Notification | Data | Both |
---|---|---|---|
Foreground | onMessageReceived |
onMessageReceived |
onMessageReceived |
Background | System tray | onMessageReceived |
Notification: system tray Data: in extras of the intent. |
Edit the app manifest
To use FirebaseMessagingService
, you need to add the following in your
app manifest:
<service android:name=".java.MyFirebaseMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service>
Also, you're recommended to set default values to customize the appearance of notifications. You can specify a custom default icon and a custom default color that are applied whenever equivalent values are not set in the notification payload.
Add these lines inside the
application
tag to set the custom default icon and custom color:
<!-- 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/ic_stat_ic_notification" /> <!-- 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" />
Android displays the custom default icon for
- All notification messages sent from the Notifications composer.
- Any notification message that does not explicitly set the icon in the notification payload.
Android uses the custom default color for
- All notification messages sent from the Notifications composer.
- Any notification message that does not explicitly set the color in the notification payload.
If no custom default icon is set and no icon is set in the notification payload, Android displays the application icon rendered in white.
Override onMessageReceived
By overriding the method FirebaseMessagingService.onMessageReceived
,
you can perform actions based on the received
RemoteMessage
object and get the message data:
Kotlin+KTX
override fun onMessageReceived(remoteMessage: RemoteMessage) { // TODO(developer): Handle FCM messages here. // Not getting messages here? See why this may be: https://goo.gl/39bRNJ Log.d(TAG, "From: ${remoteMessage.from}") // Check if message contains a data payload. if (remoteMessage.data.isNotEmpty()) { Log.d(TAG, "Message data payload: ${remoteMessage.data}") // Check if data needs to be processed by long running job if (needsToBeScheduled()) { // For long-running tasks (10 seconds or more) use WorkManager. scheduleJob() } else { // Handle message within 10 seconds handleNow() } } // Check if message contains a notification payload. remoteMessage.notification?.let { Log.d(TAG, "Message Notification Body: ${it.body}") } // Also if you intend on generating your own notifications as a result of a received FCM // message, here is where that should be initiated. See sendNotification method below. }
Java
@Override public void onMessageReceived(RemoteMessage remoteMessage) { // TODO(developer): Handle FCM messages here. // Not getting messages here? See why this may be: https://goo.gl/39bRNJ Log.d(TAG, "From: " + remoteMessage.getFrom()); // Check if message contains a data payload. if (remoteMessage.getData().size() > 0) { Log.d(TAG, "Message data payload: " + remoteMessage.getData()); if (/* Check if data needs to be processed by long running job */ true) { // For long-running tasks (10 seconds or more) use WorkManager. scheduleJob(); } else { // Handle message within 10 seconds handleNow(); } } // Check if message contains a notification payload. if (remoteMessage.getNotification() != null) { Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody()); } // Also if you intend on generating your own notifications as a result of a received FCM // message, here is where that should be initiated. See sendNotification method below. }
Override onDeletedMessages
In some situations, FCM may not deliver a message. This occurs when there are too many
messages (>100) pending for
your app on a particular device at the time it connects or if the device hasn't connected to
FCM in more than one month. In these cases,
you may receive a callback to FirebaseMessagingService.onDeletedMessages()
When the app instance receives this callback,
it should perform a full sync with your app server. If you haven't sent a message to the app on that
device within the last 4 weeks, FCM won't call onDeletedMessages()
.
Handle notification messages in a backgrounded app
When your app is in the background, Android directs notification messages to the system tray. A user tap on the notification opens the app launcher by default.
This includes messages that contain both notification and data payload (and all messages sent from the Notifications console). In these cases, the notification is delivered to the device's system tray, and the data payload is delivered in the extras of the intent of your launcher Activity.
For insight into message delivery to your app, see the FCM reporting dashboard, which records the number of messages sent and opened on Apple and Android devices, along with data for "impressions" (notifications seen by users) for Android apps.
Build send requests
After you have created a topic, either by subscribing client app instances to the topic on the client side or via the server API, you can send messages to the topic. If this is your first time building send requests for FCM, see the guide to your server environment and FCM for important background and setup information.
In your sending logic on the backend, specify the desired topic name as shown:
Node.js
// The topic name can be optionally prefixed with "/topics/".
const topic = 'highScores';
const message = {
data: {
score: '850',
time: '2:45'
},
topic: topic
};
// Send a message to devices subscribed to the provided topic.
getMessaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
Java
// The topic name can be optionally prefixed with "/topics/".
String topic = "highScores";
// See documentation on defining a message payload.
Message message = Message.builder()
.putData("score", "850")
.putData("time", "2:45")
.setTopic(topic)
.build();
// Send a message to the devices subscribed to the provided topic.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);
Python
# The topic name can be optionally prefixed with "/topics/".
topic = 'highScores'
# See documentation on defining a message payload.
message = messaging.Message(
data={
'score': '850',
'time': '2:45',
},
topic=topic,
)
# Send a message to the devices subscribed to the provided topic.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)
Go
// The topic name can be optionally prefixed with "/topics/".
topic := "highScores"
// See documentation on defining a message payload.
message := &messaging.Message{
Data: map[string]string{
"score": "850",
"time": "2:45",
},
Topic: topic,
}
// Send a message to the devices subscribed to the provided topic.
response, err := client.Send(ctx, message)
if err != nil {
log.Fatalln(err)
}
// Response is a message ID string.
fmt.Println("Successfully sent message:", response)
C#
// The topic name can be optionally prefixed with "/topics/".
var topic = "highScores";
// See documentation on defining a message payload.
var message = new Message()
{
Data = new Dictionary<string, string>()
{
{ "score", "850" },
{ "time", "2:45" },
},
Topic = topic,
};
// Send a message to the devices subscribed to the provided topic.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
// Response is a message ID string.
Console.WriteLine("Successfully sent message: " + response);
REST
POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
{
"message":{
"topic" : "foo-bar",
"notification" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message"
}
}
}
cURL command:
curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
"message": {
"topic" : "foo-bar",
"notification": {
"body": "This is a Firebase Cloud Messaging Topic Message!",
"title": "FCM Message"
}
}
}' https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
To send a message to a combination of topics,
specify a condition, which is a boolean expression that specifies the
target topics. For example, the following condition will send messages to
devices that are subscribed to TopicA
and either TopicB
or TopicC
:
"'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)"
FCM first evaluates any conditions in parentheses, and then evaluates
the expression from left to right. In the above expression, a user subscribed to
any single topic does not receive the message. Likewise, a user who does not
subscribe to TopicA
does not receive the message. These combinations do
receive it:
TopicA
andTopicB
TopicA
andTopicC
You can include up to five topics in your conditional expression.
To send to a condition:
Node.js
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
const condition = '\'stock-GOOG\' in topics || \'industry-tech\' in topics';
// See documentation on defining a message payload.
const message = {
notification: {
title: '$FooCorp up 1.43% on the day',
body: '$FooCorp gained 11.80 points to close at 835.67, up 1.43% on the day.'
},
condition: condition
};
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
getMessaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
Java
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
String condition = "'stock-GOOG' in topics || 'industry-tech' in topics";
// See documentation on defining a message payload.
Message message = Message.builder()
.setNotification(Notification.builder()
.setTitle("$GOOG up 1.43% on the day")
.setBody("$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.")
.build())
.setCondition(condition)
.build();
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);
Python
# Define a condition which will send to devices which are subscribed
# to either the Google stock or the tech industry topics.
condition = "'stock-GOOG' in topics || 'industry-tech' in topics"
# See documentation on defining a message payload.
message = messaging.Message(
notification=messaging.Notification(
title='$GOOG up 1.43% on the day',
body='$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.',
),
condition=condition,
)
# Send a message to devices subscribed to the combination of topics
# specified by the provided condition.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)
Go
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
condition := "'stock-GOOG' in topics || 'industry-tech' in topics"
// See documentation on defining a message payload.
message := &messaging.Message{
Data: map[string]string{
"score": "850",
"time": "2:45",
},
Condition: condition,
}
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
response, err := client.Send(ctx, message)
if err != nil {
log.Fatalln(err)
}
// Response is a message ID string.
fmt.Println("Successfully sent message:", response)
C#
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
var condition = "'stock-GOOG' in topics || 'industry-tech' in topics";
// See documentation on defining a message payload.
var message = new Message()
{
Notification = new Notification()
{
Title = "$GOOG up 1.43% on the day",
Body = "$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.",
},
Condition = condition,
};
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
// Response is a message ID string.
Console.WriteLine("Successfully sent message: " + response);
REST
POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
{
"message":{
"condition": "'dogs' in topics || 'cats' in topics",
"notification" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message",
}
}
}
cURL command:
curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
"notification": {
"title": "FCM Message",
"body": "This is a Firebase Cloud Messaging Topic Message!",
},
"condition": "'dogs' in topics || 'cats' in topics"
}' https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Next steps
- You can use your server to subscribe client app instances to topics and perform other management tasks. See Manage topic subscriptions on the server.