Send your android clients notifications using FCM and Amazon SNS

Narinder Mehra
5 min readJan 19, 2019

--

For teams using AWS stack, SNS is the default choice for publishing notifications/messages to various clients/endpoints. It works on a pub/sub model and thus provides all the benefits of that model.

SNS provides inherent support for publishing to kindle apps using Amazon Device Messaging (ADM). But if you wish to publish to Android apps, you will have to use android specific messaging solutions.

Firebase Cloud Messaging (FCM) is a new messaging solution provided by google to let you send messages to the android apps. It replaced the old Google Cloud Messaging solution (GCM) which has now been deprecated by google and its server and client APIs will be entirely removed from google by April 11, 2019.

Now, if you only need to send the messages to an Android app then you can use the FCM SDK with your backend application and set up local FCM servers or use Google’s servers to pass on the message. But if your intent is to send the messages to multiple recipients on different platforms using diverse mediums like notification, sms, email etc. then its better to put the FCM messaging behind Amazon SNS.

In light of the above facts, while setting up messaging API, I looked up some literature on SNS and FCM bindings and had to really work a sweat to gather all the data. So, here I am presenting all the nitty gritty of hooking up your SNS account with the FCM clients. The code snippets are in Kotlin.

My design looked something like this:

Notification/messaging set up

The components used are:

  1. Notification util — Small library to abstract the messaging APIs from the clients and pushes all the messages from clients to the request queue (RabbitMQ) for the notification service.
  2. Notification service —uses appropriate messaging method to send the messages. Compiles with AWS SDK and formats the messages wrt to its requirements.

Notes:

  1. I simplified the design a bit, as in SNS can send messages to either a topic or directly to an endpoint using SMS or Email.
  2. Similarly topic subscription can be via FCM token(devices) or direct (SMS, Email)

To send the messages to Apps via SNS-FCM, following steps needs to be taken:

  1. Register the App with FCM
  2. Register the App with AWS (While creating platform application, select GCM as the push notification platform and providing your Server API key in the API key field.)
  3. Create a topic in AWS
  4. Create the endpoint for the device using FCM key.
  5. Subscribe the newly created endpoints in step 4 to the topic. (This step is vital as it binds the devices containing the app to the SNS topic).

Viola! your pipeline is ready. Now you can send all the messages you want to that posh android app you are building, using either the AWS SNS dashboard or programmatically (which is what we are going to discuss in the next section)

Sending the messages via backend applications is similar though a bit tricky. The first 3 points described above, even though could be done programmatically but since they are not going to change much so we can also create them on console and do the rest of configuration in the code.

Create a config class to access SNS APIs

AWS provides a SDK to communicate with SNS. To use that, we need three things, the region, access key and password, which can be provided using a property file.

@Configuration
class SNSConfig {

lateinit var region: String

lateinit var accessKeyId: String

lateinit var secretAccessKey: String


fun client(): AmazonSNS {
return AmazonSNSClientBuilder.standard().withRegion(region)
.withCredentials(AWSStaticCredentialsProvider(BasicAWSCredentials(accessKeyId, secretAccessKey)))
.build()
}
}

Create platform endpoint in AWS

SNS recognises the different entities using endpoints or ARN (Amazon Resource Names). So whether its a topic, application or device, everything needs to be registered with SNS to get the ARN.

To register our device with AWS we need to create platform endpoint. To create the platform endpoint (device endpoint), we need 1. applicationARN which is generated when we register our application with AWS and 2. FCM Token given to the application by FCM server. This token keeps on changing depending on various pre-defined scenarios, so its always recommended that the android app publishes this token to the backend as soon as it receives a fresh token from FCM and every time backend receives it, a new endpoint will be created. So when the event is published to the endpoint/topic, it will always go to the latest recipients.

val platformEndpointRequest = CreatePlatformEndpointRequest();
platformEndpointRequest.token = fcmToken;
platformEndpointRequest.platformApplicationArn = applicationArn;
res = snsConfig.client().createPlatformEndpoint(platformEndpointRequest);

createPlatformEndpoint returns the newly created ARN in CreatePlatformEndpointResult structure.

Send message to the Endpoint

Now a message can be published against the endpoint ARN generated in previous step.

val publishRequest = PublishRequest()
publishRequest.targetArn = res.endpointArn
publishToSNS(publishRequest, message, subject)

Here res is the CreatePlatformEndpointResult which we got in the previous step. I will show the content of publishToSNS() shortly.

Subscribe to a Topic

If we wish to send the messages to a single app then the above method will suffice but if we wish to send it to multiple recipients then publishing it to a Topic makes more sense.

As Topic creation is a one time activity, so it can either be done via SNS console or programmatically. I preferred to do it over console.

Once you have the Topic ARN, you can add subscriptions to that topic.

val res = createPlatformEndpoint(fcmToken, applicationARN)
snsConfig.client().subscribe(SubscribeRequest(topicARN, "application", res.endpointArn))

Publish to a Topic

Publishing to Topic ARN will result in all the subscribers receiving that message.

val publishRequest = PublishRequest()
publishRequest.topicArn = topicARN
publishToSNS(publishRequest, message, subject)

publishToSNS() is the same method that we used to publish to individual endpoint.

Publishing to SNS

Publishing to FCM is a bit different than publishing to GCM. The payload and message structures understood by FCM are not the same as GCM.

fun publishToSNS(publishRequest: PublishRequest, message: String, subject: String) {
val snsClient = snsConfig.client()
publishRequest.messageStructure = "json"
val payload = "\\\"notification\\\":{\\\"text\\\":\\\"{$message}\\\" , \\\"title\\\":\\\"{$subject}\\\"}"
publishRequest.message = "{\"default\": \"default\",\"GCM\":\"{$payload}\"}"
snsClient.publish(publishRequest)
}

If you wish to send a fine grained payload like the following:

{
"message":{
"token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
"notification":{
"title":"Portugal vs. Denmark",
"body":"great match!"
},
"data" : {
"Nick" : "Mario",
"Room" : "PortugalVSDenmark"
}
}
}

Then you can use the following way:

fun sendToFcm(publishRequest: PublishRequest, payload: FcmPayload) {
val payloadJson = Gson().toJson(payload).toString()
val stringifiedPayload = payloadJson.replace("\"", "\\\"")

val publishRequest = PublishRequest()
publishRequest.messageStructure = "json"
publishRequest.message = "{\"GCM\":\"$stringifiedPayload\"}"
publishRequest.targetArn = res.endpointArn
val snsClient = snsConfig.client()
snsClient.publish(publishRequest)
}

where FcmPayload is:

data class FcmPayload (
val notification : FcmNotification,
val data : Map<String, String>? = null
)

That’s it. You are ready with your simple messaging system. Its a bit tedious to set up but once its done, it works like a charm

Please do not hesitate to give feedback, share your own opinion or just turn up to say Hello!

— Narinder

--

--

Narinder Mehra

Principal backend engineer @Pharmeasy. Avid reader. Movie enthusiast. Love travelling, exploring new cultures, languages.