Send push notifications on Android

Showing Java examples.

In this tutorial, we make a PubNub message arrive on an Android device as a system notification. We get a Firebase Cloud Messaging (FCM) registration token from the device, then register that token on one PubNub channel with Mobile Push Notifications. We publish a message carrying an FCM push payload to the same channel, and watch the notification appear in the status bar. Every step needs a device or emulator that has Google Play services, because FCM won't issue a registration token without it.

Before you begin​

You need:

  • An Android Studio project with the PubNub Kotlin or Java SDK already added. If you haven't added it yet, follow Quickstart first, in Kotlin or Java, and come back here.
  • A PubNub keyset. A keyset is the set of publish, subscribe, and secret keys that identifies your application to the PubNub network. You'll need its publish key and subscribe key. If you don't have a keyset yet, follow Set up your account to create one.
  • A Firebase project. Create one in the Firebase console with Create a project, then add your Android app to it so Firebase knows its package name.
  • An Android device, or an emulator image that includes Google Play services. Emulator images without the Play Store can't run FCM.

We'll use one channel throughout, called push-tutorial, and one device.

Give PubNub your Firebase credentials​

PubNub sends the notification to FCM on your behalf, so it needs credentials for your Firebase project. Those credentials are a service-account private key file that you generate in Firebase and upload to your keyset. This is a one-time setup task per keyset.

First, create the service account and download its key:

  1. In the Firebase console, open your project.
  2. Click the settings icon next to Project Overview and select Project settings.
  3. Open the Service accounts tab.
  4. Under All service accounts, click the link that opens the Google Cloud Platform IAM page for the project. The default Firebase service account has near-admin privileges over the whole project, and PubNub only needs to send messages, so create a dedicated account instead.
  5. Create a new service account, and give it the Firebase Cloud Messaging API Admin role. Click Done.
  6. Back in the Firebase console Service accounts tab, open the new service account and click Generate new private key. Firebase downloads a JSON private key file. Keep it somewhere you can find it again.

Now hand that key to PubNub:

  1. Open the Admin Portal and select the app and keyset you'll use for this tutorial.
  2. Find the Mobile Push Notifications section and enable it.
  3. In the Firebase Cloud Messaging part of that section, use Private key file to upload the JSON file you just downloaded.
  4. Save the keyset.

Notice that this configuration lives on the keyset, not on a channel. Every channel on this keyset now pushes through this one Firebase project.

Connect the app to Firebase​

Your app needs the Firebase configuration file and the FCM library.

  1. In the Firebase console, go to Project settings > Your apps, select your Android app, and download google-services.json.
  2. In Android Studio, switch the Project window to the Project view, and drop google-services.json into your app module's root directory, next to the module's build.gradle.kts file.
  3. Add the google-services Gradle plugin and the Firebase Cloud Messaging dependency. The Gradle mechanics aren't PubNub-specific, so follow Firebase's own Add Firebase to your Android project and add the firebase-messaging library.
  4. Sync the project.

If the sync fails complaining that google-services.json is missing, the file is in the wrong directory. It belongs in the app module, not the project root.

Get the device's FCM registration token​

FCM identifies a single installation of your app on a single device by a registration token. That token is what you register with PubNub, so this is the first thing your app needs.

Declare two things in AndroidManifest.xml: permission to post notifications, which Android 13 and later require, and the service that receives token updates.

1<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
3 <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
4
5 <application>
6
7 <service
8 android:name=".MyFirebaseMessagingService"
9 android:exported="false">
10 <intent-filter>
11 <action android:name="com.google.firebase.MESSAGING_EVENT" />
12 </intent-filter>
13 </service>
14
15 </application>
show all 16 lines

Now ask for the token in your activity. We do this in onCreate so it runs every time the app starts, and we ask for the notification permission in the same place. The code blocks in this tutorial show class members rather than whole files, so let Android Studio add the missing imports as you paste them. The complete files are at the end of this page.

1public class MainActivity extends AppCompatActivity {
2 private static final String TAG = "PushTutorial";
3
4 @Override
5 protected void onCreate(Bundle savedInstanceState) {
6 super.onCreate(savedInstanceState);
7
8 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
9 ActivityCompat.requestPermissions(
10 this, new String[]{Manifest.permission.POST_NOTIFICATIONS}, 1);
11 }
12
13 FirebaseMessaging.getInstance().getToken().addOnCompleteListener(task -> {
14 if (!task.isSuccessful()) {
15 Log.w(TAG, "Fetching FCM registration token failed", task.getException());
show all 22 lines

FCM can also replace a token later, for example after the user reinstalls or clears the app's data. When that happens, FCM calls onNewToken on the service you declared in the manifest. Create that class now:

1public class MyFirebaseMessagingService extends FirebaseMessagingService {
2 private static final String TAG = "PushTutorial";
3
4 @Override
5 public void onNewToken(@NonNull String token) {
6 Log.d(TAG, "FCM issued a new registration token: " + token);
7 }
8}

A replaced token invalidates the registration you made with the old one. A production app registers the new token here the same way you're about to register the first one, and removes the old registration. See Available SDKs for the mobile push API reference for your platform.

Run the app now. In Logcat, filter on PushTutorial, and you should see a line like:

FCM registration token: dK3f9...:APA91bH...

That long string is the token. If you see Fetching FCM registration token failed instead, the device has no Google Play services.

Register the token on a PubNub channel​

PubNub keeps a list of device tokens per channel name. Registering the token on push-tutorial is what tells PubNub to forward pushes from that channel to this device. Registration is per channel, so one call covers one channel.

Create the PubNub client and register the token you just received:

1public class MainActivity extends AppCompatActivity {
2 private static final String TAG = "PushTutorial";
3 private static final String CHANNEL = "push-tutorial";
4
5 private PubNub pubnub;
6
7 @Override
8 protected void onCreate(Bundle savedInstanceState) {
9 super.onCreate(savedInstanceState);
10
11 try {
12 PNConfiguration config = PNConfiguration
13 .builder(new UserId("push-tutorial-user"), "YOUR_SUBSCRIBE_KEY")
14 .publishKey("YOUR_PUBLISH_KEY")
15 .build();
show all 35 lines

Call registerForPush(token) from the getToken() callback in step 3, right after the line that logs the token.

Replace YOUR_PUBLISH_KEY and YOUR_SUBSCRIBE_KEY with the keys from the keyset you configured in step 1. Push works only for the keyset that holds the Firebase private key, so keys from a different keyset produce a registration that never delivers anything.

PNPushType.FCM is what selects Firebase. The deviceId is the FCM registration token, not the Android device ID.

Run the app again. Logcat should now show, after the token line:

Registered for push on push-tutorial

Publish a message with an FCM push payload​

A publish becomes a push notification when its payload contains a pn_fcm key. PubNub's push gateway reads that key, looks up the tokens registered on the channel, and forwards the request to FCM. Anything else in the message travels over pub/sub as usual.

This is the payload we're going to send, on the wire:

1{
2 "pn_fcm": {
3 "notification": {
4 "title": "Chat Invitation",
5 "body": "John invited you to chat"
6 }
7 }
8}

The title and body are what the device displays. Keep the whole payload within the limit.

ItemLimit
Push credentials per keyset1 APNs certificate and 1 FCM key
Push notification payload size2 KB for APNs, 4 KB for FCM

The Kotlin and Java SDKs build that shape for you with PushPayloadHelper, so you don't assemble the JSON by hand. Add a method that builds the payload and publishes it:

1    private void publishPushMessage() {
2 PushPayloadHelper.FCMPayloadV2.Notification notification =
3 new PushPayloadHelper.FCMPayloadV2.Notification();
4 notification.setTitle("Chat Invitation");
5 notification.setBody("John invited you to chat");
6
7 PushPayloadHelper.FCMPayloadV2 fcmPayload = new PushPayloadHelper.FCMPayloadV2();
8 fcmPayload.setNotification(notification);
9
10 PushPayloadHelper payloadHelper = new PushPayloadHelper();
11 payloadHelper.setFcmPayloadV2(fcmPayload);
12
13 Map<String, Object> payload = payloadHelper.build();
14
15 pubnub.channel(CHANNEL).publish(payload)
show all 19 lines

payloadHelper.build() returns the map that matches the JSON above, with pn_fcm at the top level.

We need this publish to happen while the app is in the background. FCM only puts a notification in the status bar when the app isn't in the foreground, so call publishPushMessage() from onStop(), which Android runs as the activity leaves the screen:

1    @Override
2 protected void onStop() {
3 super.onStop();
4 publishPushMessage();
5 }

The device that publishes is also registered on push-tutorial, so it receives the push it just triggered. That's exactly what we want here. One device does everything, and you see the result.

See the notification​

  1. Run the app on your physical device, or on an emulator image with Google Play services.
  2. Tap Allow when Android asks whether the app may send notifications.
  3. Watch Logcat and wait for Registered for push on push-tutorial. Don't skip this. If you background the app before registration finishes, there's no token on the channel yet and nothing to push to.
  4. Press the device's Home button.

Within a second or two, a notification appears in the status bar:

Chat Invitation
John invited you to chat

Pull down the notification shade to see it in full. Logcat also shows Published with push payload, which confirms the publish itself succeeded.

Try it again: open the app and press Home once more. The same notification arrives every time, because onStop() publishes again and the registration is still in place.

What happened​

Your device and your keyset played different parts in the same round trip:

  1. Firebase gave the app a registration token that identifies this installation on this device.
  2. Your app sent that token to PubNub with addPushNotificationsOnChannels, which associated the token with the channel push-tutorial.
  3. Your app published a message to push-tutorial containing a pn_fcm key.
  4. PubNub's push gateway spotted pn_fcm, looked up the tokens registered on push-tutorial, and found yours.
  5. PubNub authenticated to FCM with the private key you uploaded in step 1 and forwarded the notification.
  6. FCM delivered it to your device, and because the app was in the background, Android displayed it in the status bar.

Notice that nothing subscribed to push-tutorial in this tutorial. Push doesn't need an open connection, and that's the point of it. The notification reaches a device whose app isn't running in the foreground. For the full model, including what happens when a device is both subscribed and push-registered, see Mobile push notifications.

Complete files​

Both classes, with the imports they need.

MainActivity.java:

1import android.Manifest;
2import android.os.Build;
3import android.os.Bundle;
4import android.util.Log;
5
6import androidx.appcompat.app.AppCompatActivity;
7import androidx.core.app.ActivityCompat;
8
9import com.google.firebase.messaging.FirebaseMessaging;
10import com.pubnub.api.PubNubException;
11import com.pubnub.api.UserId;
12import com.pubnub.api.enums.PNPushType;
13import com.pubnub.api.java.PubNub;
14import com.pubnub.api.java.v2.PNConfiguration;
15import com.pubnub.api.models.consumer.push.payload.PushPayloadHelper;
show all 93 lines

MyFirebaseMessagingService.java:

1import android.util.Log;
2
3import androidx.annotation.NonNull;
4
5import com.google.firebase.messaging.FirebaseMessagingService;
6
7public class MyFirebaseMessagingService extends FirebaseMessagingService {
8 private static final String TAG = "PushTutorial";
9
10 @Override
11 public void onNewToken(@NonNull String token) {
12 Log.d(TAG, "FCM issued a new registration token: " + token);
13 }
14}

Troubleshooting​

If no notification arrives:

  • The device or emulator has no Google Play services. FCM depends on it. Logcat shows Fetching FCM registration token failed and there's no token to register. Use a physical device, or an emulator image listed with the Play Store.
  • You backgrounded the app before the registration succeeded. Reopen the app, wait for Registered for push on push-tutorial in Logcat, then press Home. To confirm which channels a token is currently registered on, see Check push device registration.
  • FCM replaced the token. If onNewToken logged a token that differs from the one you registered, the old registration is stale and PubNub is pushing to a token FCM no longer routes. Register the new token.
  • The keys and the Firebase credentials don't match. The private key you uploaded in step 1 must come from the same Firebase project as the google-services.json in your app. The publish and subscribe keys in your code must also belong to the keyset you uploaded it to. A mismatch produces a successful publish and no notification.
  • You denied the notification permission. On Android 13 and later, a denied POST_NOTIFICATIONS permission means the push arrives but nothing is displayed. Grant it in the app's system settings.
  • The app was in the foreground. FCM hands a notification to your app instead of the status bar when the app is in the foreground. Press Home first.

If the publish succeeds and you still see nothing, ask PubNub what FCM said. Debug push notification messages shows how to read the errors FCM returns to the push gateway.

Next steps​

You've delivered a PubNub message to an Android device as a push notification. From here:

Was this page useful?

Last updated on