Firebase notifications behave differently depending on the foreground/background
state of the receiving app. If you want foregrounded apps to receive
notification messages or data messages, you’ll need to write code to handle
the
onMessageReceived
callback.
For an explanation of the difference between notification and data messages,
see
Message types
.
Handling 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.
|
For more information about message types, see
Notifications and
data messages
.
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.
Receive FCM messages in direct boot mode
Developers who want to send FCM messages to apps even before the
device is unlocked can enable an Android app to receive messages when the device
is in direct boot mode. For instance, you might want users of your app to
receive alarm notifications even on a locked device.
When building out this use case, observe the general
best practices and restrictions for direct boot mode
. It's
particularly important to consider the
visibility
of direct boot-enabled
messages; any user with access to the device can view these messages without
entering user credentials.
Prerequisites
- The device must be set up for direct boot mode.
- The device must have a recent version of Google Play services installed (19.0.54 or later).
- The app must be using the FCM SDK (
com.google.firebase:firebase-messaging
) to receive FCM messages.
Enable direct boot mode message handling in your app
In the app-level Gradle file, add a dependency on the FCM direct boot support library:
implementation 'com.google.firebase:firebase-messaging-directboot:20.2.0'
Make the app's
FirebaseMessagingService
direct boot aware by adding the
android:directBootAware="true"
attribute in the app manifest:
<service
android:name=".java.MyFirebaseMessagingService"
android:exported="false"
android:directBootAware="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
It is important to ensure that this
FirebaseMessagingService
can run in direct boot mode. Check
for the following requirements:
- The service should not access credential protected storage while running in direct boot mode.
- The service should not attempt to use components, such as
Activities
,
BroadcastReceivers
,
or other
Services
that are not marked as direct boot aware while running in direct boot mode.
- Any libraries that the service uses must also not access credential protected storage nor
call non-directBootAware components while running in direct boot mode. This means that any libraries
the app uses that are called from the service either will need to be direct boot aware, or
the app will need to check if it’s running in direct boot mode and not call them in that mode.
For example, Firebase SDKs work with direct boot (they can be included in an app without
crashing it in direct boot mode), but many Firebase APIs do not support being called in direct
boot mode.
- If the app is using a custom
Application
, the
Application
will also need to be direct boot
aware (no access to credential protected storage in direct boot mode).
For guidance on sending messages to devices in direct boot mode, see
Send direct boot-enabled messages
.