Android Firebase Cloud Messaging in Python

By Rex Resurreccion Oct 10, 2022
Android Firebase Cloud Messaging in Python

How to send Firebase Cloud Messages in Python programming? How to test Firebase Cloud Messaging in Android application?

In this tutorial I will demonstrate on how to set up a Firebase project and send push notification to Android devices through Cloud Messaging service. We will use the Firebase Admin SDK for Python. Also, I will create a simple Android app project in IntelliJ IDE to receive messages in a virtual device.

Create a Firebase Project and Service account

Create a Firebase Project and Set up Cloud Messaging Service (FCM)

You will need to sign up for a Google account if you don’t have one yet. Then log in to Firebase Console. Click on Add Project, then give your project a name. For this tutorial I named my project “My First App”. I disabled google analytics for now and then hit Create project.

Firebase project settings service account

Once you have your project created, go to settings.

Firebase generate private key for API access

Under service accounts, generate private key for Python that we will be using to allow sending API actions. We’ll need to download this and copy the JSON file later into our Python project.

Python service integration to Firebase Cloud Messaging (FCM)

Python service integration to Firebase Cloud Messaging (FCM)

For the second part, I created a new Python project in my PyCharm IDE and named it “firebase-myapp”. This will act as a service for sending push notifications to my Android application later.

In my above screenshot, you will see my project structure. I copied over the generated private key earlier and named it “firebase-myapp-service-account.json”. And I used Poetry To install Firebase Admin.

You can download the code base in my GitHub repo, but first I wanted to discuss the parts of my implementation here.

Using the Service Account private key for authentication

from typing import Optional, List, Dict
import os

import firebase_admin
from firebase_admin.credentials import Certificate
from firebase_admin.messaging import Message, MulticastMessage, Notification, FCMOptions, AndroidConfig, BatchResponse
from firebase_admin import messaging


certificate: Certificate = Certificate(os.getenv("FIREBASE_SA_JSON_FILE", "serviceAccountKey.json"))
firebase_admin.initialize_app(credential=certificate)
device_token: str = os.getenv("ANDROID_DEVICE_TOKEN1")

To start with, In my snippet I’m importing all the modules and classes from firebase_admin . Notice the important lines here for the initialization of credentials and the device token device_token. For this tutorial, I am going to manually set up the device token here. But, imagine that this is automatically captured by your server when someone installed your application in their device (e.g. mobile phone) and you can store this in a database.

def send_message(
    data: Optional[Dict[str, str]] = None,
    notification: Optional[Notification] = None,
    token: Optional[str] = None,
    fcm_options: Optional[FCMOptions] = None,
    android_config: Optional[AndroidConfig] = None,
    dry_run: bool = False
) -> str:
    if not data and not notification and not (android_config and android_config.data):
        raise ValueError("No data or message to send")

    message: Message = Message(
        data=data,
        notification=notification,
        token=token,
        fcm_options=fcm_options,
        android=android_config,
    )

    return messaging.send(message, dry_run=dry_run) # return message ID


def send_multicast_message(
    data: Optional[Dict[str, str]] = None,
    notification: Optional[Notification] = None,
    tokens: Optional[List[str]] = None,
    fcm_options: Optional[FCMOptions] = None,
    android_config: Optional[AndroidConfig] = None,
    dry_run: bool = False
) -> List[str]:
    if not data and not notification and not (android_config and android_config.data):
        raise ValueError("No data or message to send")

    multicast_message: MulticastMessage = MulticastMessage(
        data=data,
        notification=notification,
        tokens=tokens,
        fcm_options=fcm_options,
        android=android_config,
    )

    batch_response: BatchResponse = messaging.send_multicast(multicast_message, dry_run=dry_run)
    response_message_ids: List[str] = [
        send_response.message_id for send_response in batch_response.responses
    ] if batch_response.responses else []

    if batch_response.failure_count:
        print(
            f"One or more messages has failed to send. "
            f"Failed: {batch_response.failure_count} "
            f"Message IDs: {response_message_ids}"
        )

    return response_message_ids

Next are the functions send_message and send_multicast_message. I made two examples, the first function send_message will send a message to a single device with a given token. This uses the messaging.send(message, dry_run=dry_run). And the second function send_multicast_message will send the message to multiple devices by passing a list of tokens. This uses the messaging.send_multicast(multicast_message, dry_run=dry_run)

Sending Message to Android device

if __name__ == '__main__':
    message_id: str = send_message(
        token=device_token,
        notification=Notification(
            title="Hello World",
            body="This is my first app",
        ),
    )
    print(f"Message sent: {message_id}")

Let’s take a look at this example function call above. Here I can send a Notification message using the Notification class.

if __name__ == '__main__':
    message_id: str = send_message(
        token=device_token,
        data={
            "device_id": "1234",
            "message": "This is my first app",
        },
    )
    print(f"Message sent: {message_id}")

On the other hand, if you need to send an object of data, you can also directly pass a dictionary instead.

Sending Message to multiple Android devices

if __name__ == '__main__':
    message_ids: List[str] = send_multicast_message(
            tokens=[device_token],
            android_config=AndroidConfig(
                data={
                    "device_id": "1234",
                    "message": "This is my first app",
                },
                collapse_key="test-myapp-message",
                ttl=5000,
            ),
    )
    print(f"Messages sent: {message_ids}")

Compared to sending messages to a single device, here I can pass a list [device_token] of tokens for batch messaging. Also, I used the AndroidConfig to send data along with other configurations. The collapse_key is an identifier for a group of messages that can be collapsed, so that only the last message is sent when delivery can be resumed. And the ttl is the time-to-live duration of the message.

Android app for testing FCM with virtual devices

Now that we have our Python code all setup, we need a way to test our service when we send the notification to Firebase Cloud Messaging. Let’s follow the tutorial in IntelliJ on how to create an Android application and to install its components.

Create Android application project in IntelliJ

You can select any of the Project Template for your Android application. For testing purposes, we are not going to dig into the UI of the device. We will only use the android.util.Log to view the push notification messages and other logs in the terminal.

Configure Android application project in IntelliJ

Configure your project. Give it a name and select the location where you want to save it. It’s important to mention that for this tutorial, I selected Java as the language and the minimum SDK has to be at least API version 19. Then click Finish.

Add Android application to Firebase project

Firebase project general settings

Before we can continue with setting up an Android app project, first we need to register this in Firebase and download the google-services.json.

Add Firebase to your app

Again, under Project settings, General section. Scroll down, and click on Add app, then select the Android icon.

Add Android application to Firebase project

Go through the registration process for your app. The package name will be the name of the Android Java project we just created in IntelliJ earlier. That is com.example.androidappfortesting.

Firebase project add android apps and download google-services.json

Once you have your Android app added to your Firebase console, you should see something similar above. Then you need to click on download google-services.json. Copy this inside the app folder of your Android Java project.

Add Google Service Plugin and Firebase SDK in Android application

Java Android application for testing Firebase Cloud Messaging (FCM)

Back to our code base after adding this in Firebase console and downloaded the google-services.json. Next is to make the config file accessible to Firebase SDK. And to do that, we’ll add Google services plugin as a buildscript dependency.

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        google()  // Google's Maven repository
        jcenter()
        mavenCentral()  // Maven Central repository
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.1"
        classpath 'com.google.gms:google-services:4.3.13'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()  // Google's Maven repository
        jcenter()
        mavenCentral()  // Maven Central repository
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

Be aware that there are two build.gradle files in your project. There is one in the root-level of your project and another one inside the app folder. You will need to first add this as a dependency in the root-level. For this tutorial, that is in AndroidAppForTesting/build.gradle.

plugins {
    id 'com.android.application'
    id 'com.google.gms.google-services'
}

android {
    compileSdkVersion 33
    buildToolsVersion "33.0.0"

    defaultConfig {
        applicationId "com.example.androidappfortesting"
        minSdkVersion 19
        targetSdkVersion 33
        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
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'com.google.android.material:material:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.navigation:navigation-fragment:2.2.2'
    implementation 'androidx.navigation:navigation-ui:2.2.2'
    implementation 'com.google.firebase:firebase-messaging'
    implementation 'com.google.firebase:firebase-analytics'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation platform('com.google.firebase:firebase-bom:30.4.1')
}

And then in app folder build.gradle file, add the Google services plugin and Firebase SDK as dependency. For this tutorial, that is in AndroidAppForTesting/app/build.gradle. In my above configuration file, focus on the plugins and dependencies sections where I added all the google stuff, including this line for the FCM `implementation 'com.google.firebase:firebase-messaging'. You can find a bunch of other Gradle dependencies in their library.

Finally, sync your Android project with the changes we’ve made in the Gradle files. Run the Build Project in IntelliJ IDE.

Logging the Token and Message Received in Android application

In your project, create a service class in the main Java package that extends the `FirebaseMessagingService class. Add these two methods onNewToken and onMessageReceived as mentioned in the documentation for monitoring a generated token and for receiving a message.

public class MyFirebaseMessagingService extends FirebaseMessagingService {
    public static final String TAG = "Push-Event";
    public static final String CHANNEL_ID = "testing_notification";
    public static final String CHANNEL_NAME = "com.example.androidappfortesting";

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

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        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());
        }

        // Check if message contains a Notification payload.
        if (remoteMessage.getNotification() != null) {
            String title = remoteMessage.getNotification().getTitle();
            String description = remoteMessage.getNotification().getBody();
            Log.d(TAG, "Message Notification Title: " + title);
            Log.d(TAG, "Message Notification Description: " + description);
        }
    }
}

In this example, I am logging the Token and Message (data and notification) for verifying that our Android Firebase Cloud Messaging sent in Python is successful. The first log will be important Log.d(TAG, "Refreshed token: " + token);. Keep in mind that you will see the log once when you first run your virtual device. If you want to see the log again, you will need to uninstall the Android app and run the device again.

Declaring FirebaseMessagingService into the App Manifest

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

In order for the service class MyFirebaseMessagingService to work, we need to declare this in the AndroidManifest file, that’s inside the app main folder. Add the XML inside the <application> ... </application> tag.

Creating the Virtual Device for testing in IntelliJ

We have come to the exciting part! We can now create our virtual device that can receive messages from our Android Firebase Cloud Messaging sent from the Python code we set up earlier. Again, I am building and running my Android application in IntelliJ IDE

Open AVD Manager for testing Android Firebase Cloud Messaging in Python

On the top right drop down menu, click on Open AVD Manager.

Create virtual device for testing Android Firebase Cloud Messaging in Python

Follow the steps in the panel for creating the virtual device.

Virtual device Phone for testing Android Firebase Cloud Messaging in Python

Select the applicable device that you will use for testing. For this tutorial, I selected a Pixel 2 phone.

Download system image for virtual device

Choose which system image you want to use for your device to download. For Pixel 2 phone, I downloaded the one for Android 11.0.

Verify configuration and display for virtual device

Verify your configuration and select how you want your device display and hit Finish.

Android virtual devices
Start playing your android virtual device

Select your device on the menu and click on the play button to start connecting to AVD emulator.

If everything is setup correctly, you’ll be able to see something similar above. And just like in a regular phone, open on the menu the Android Java project.

Sending Android Firebase Cloud Messaging in Python

In the early part of my tutorial, I have created two functions for this demo. These are send_message and send_multicast_message. Now, I wanted to show some examples on how to use these functions to send push notification.

if __name__ == '__main__':
    message_id: str = send_message(
        token=device_token,
        notification=Notification(
            title="Hello World",
            body="This is my first app",
        ),
        data={
            "device_id": "1234",
            "message": "This is my first app",
        },
    )
    print(f"Message sent: {message_id}")

On my first example above, I used send_message to send a single push message with a body and title wrapped in Notification class. In addition, I added some data as dict with device_id and message .

As a result, I get a message_id from FCM as a response if it’s a success.

if __name__ == '__main__':
    message_ids: List[str] = send_multicast_message(
            tokens=[device_token],
            android_config=AndroidConfig(
                data={
                    "device_id": "1234",
                    "message": "This is my first app",
                },
                collapse_key="test-myapp-message",
                ttl=5000,
            ),
            # dry_run=True,
    )
    print(f"Messages sent: {message_ids}")

For a more complex example above, I am able to send push messages to multiple devices by passing a list of tokens to function send_multicast_message. This time I am passing new parameters wrapped in AndroidConfig class. Aside from data, I have collapse_key parameter that allows sending the latest message only when there are multiple with the same collapse key. Optional, is the dry_run parameter of send_multicast, that can perform all the usual validations and emulates the send operation, but not actually send the message.

And this time I get a list of message_id as a result, for the multiple messages that was sent.

Viewing push notification messages in Logcat and Firebase console Dashboard

Testing using Logcat to view messages

At this point, we have done enough so we can start sending Push notifications and receive messages in our Android application. To view this, open the Logcat panel in IntelliJ IDE while your Android app is running.

Testing using Logcat to view messages

Based on my example push notification, you should see a result something similar above. You will notice the Notification and data received with device_id and message.

Another helpful tool is the Dashboard in your Firebase console. In here, it shows that two messages was sent and we’ve done this through our Python app.

Summary

In this tutorial, we just learned on how to create a project in Firebase console. We used Firebase messaging service to send push notification. We learned to implement an Android Firebase Cloud Messaging (FCM) in Python and test this by creating a simple Android Java application.

© YippeeCode.com 2020