Google Firebase Cloud Messaging

The google firebase cloud messaging connector provides a way to send notifications https://firebase.google.com/docs/cloud-messaging/ .

Early state

The whole FCM server implementation doc is a bit unclear. This connector is build from scratch following the documentation. So the error parsing, the condition builder, the apns object and some other object/case class could (and possibly will) be improved.

Reported issues

Tagged issues at Github

Artifacts

sbt
libraryDependencies += "com.lightbend.akka" %% "akka-stream-alpakka-google-fcm" % "0.20"
Maven
<dependency>
  <groupId>com.lightbend.akka</groupId>
  <artifactId>akka-stream-alpakka-google-fcm_2.12</artifactId>
  <version>0.20</version>
</dependency>
Gradle
dependencies {
  compile group: 'com.lightbend.akka', name: 'akka-stream-alpakka-google-fcm_2.12', version: '0.20'
}

Usage

Possibly needed imports for the following codes.

Scala
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.alpakka.google.firebase.fcm.FcmFlowModels.FcmFlowConfig
import akka.stream.alpakka.google.firebase.fcm.{FcmFlowModels, FcmNotification}
import akka.stream.alpakka.google.firebase.fcm.FcmNotificationModels._
import akka.stream.scaladsl.{Sink, Source}

import scala.collection.immutable
import scala.concurrent.Future
Full source at GitHub
Java
import akka.actor.ActorSystem;
import akka.japi.Pair;
import akka.stream.ActorMaterializer;
import akka.stream.alpakka.google.firebase.fcm.FcmFlowModels;
import akka.stream.alpakka.google.firebase.fcm.FcmNotification;
import akka.stream.alpakka.google.firebase.fcm.FcmNotificationModels;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
Full source at GitHub

Prepare the actor system and materializer.

Scala
implicit val system = ActorSystem()
implicit val mat = ActorMaterializer()
Full source at GitHub
Java
ActorSystem system = ActorSystem.create();
ActorMaterializer materializer = ActorMaterializer.create(system);
Full source at GitHub

Prepare your credentials for access to FCM.

Scala
val privateKey =
  """-----BEGIN RSA PRIVATE KEY-----
    |MIIBOgIBAAJBAJHPYfmEpShPxAGP12oyPg0CiL1zmd2V84K5dgzhR9TFpkAp2kl2
    |9BTc8jbAY0dQW4Zux+hyKxd6uANBKHOWacUCAwEAAQJAQVyXbMS7TGDFWnXieKZh
    |Dm/uYA6sEJqheB4u/wMVshjcQdHbi6Rr0kv7dCLbJz2v9bVmFu5i8aFnJy1MJOpA
    |2QIhAPyEAaVfDqJGjVfryZDCaxrsREmdKDlmIppFy78/d8DHAiEAk9JyTHcapckD
    |uSyaE6EaqKKfyRwSfUGO1VJXmPjPDRMCIF9N900SDnTiye/4FxBiwIfdynw6K3dW
    |fBLb6uVYr/r7AiBUu/p26IMm6y4uNGnxvJSqe+X6AxR6Jl043OWHs4AEbwIhANuz
    |Ay3MKOeoVbx0L+ruVRY5fkW+oLHbMGtQ9dZq7Dp9
    |-----END RSA PRIVATE KEY-----""".stripMargin
val clientEmail = "test-XXX@test-XXXXX.iam.gserviceaccount.com"
val projectId = "test-XXXXX"
val fcmConfig = FcmFlowConfig(clientEmail, privateKey, projectId, isTest = false, maxConcurentConnections = 100)
Full source at GitHub
Java
String privateKey =
    "-----BEGIN RSA PRIVATE KEY-----\n"
        + "MIIBOgIBAAJBAJHPYfmEpShPxAGP12oyPg0CiL1zmd2V84K5dgzhR9TFpkAp2kl2\n"
        + "9BTc8jbAY0dQW4Zux+hyKxd6uANBKHOWacUCAwEAAQJAQVyXbMS7TGDFWnXieKZh\n"
        + "Dm/uYA6sEJqheB4u/wMVshjcQdHbi6Rr0kv7dCLbJz2v9bVmFu5i8aFnJy1MJOpA\n"
        + "2QIhAPyEAaVfDqJGjVfryZDCaxrsREmdKDlmIppFy78/d8DHAiEAk9JyTHcapckD\n"
        + "uSyaE6EaqKKfyRwSfUGO1VJXmPjPDRMCIF9N900SDnTiye/4FxBiwIfdynw6K3dW\n"
        + "fBLb6uVYr/r7AiBUu/p26IMm6y4uNGnxvJSqe+X6AxR6Jl043OWHs4AEbwIhANuz\n"
        + "Ay3MKOeoVbx0L+ruVRY5fkW+oLHbMGtQ9dZq7Dp9\n"
        + "-----END RSA PRIVATE KEY-----";
String clientEmail = "test-XXX@test-XXXXX.iam.gserviceaccount.com";
String projectId = "test-XXXXX";
FcmFlowModels.FcmFlowConfig fcmConfig =
    new FcmFlowModels.FcmFlowConfig(clientEmail, privateKey, projectId, false, 100);
Full source at GitHub

The last two parameters in the above example are the predefined values. You can send test notifications (so called validate only). And you can set the number of maximum concurrent connections. There is a limitation in the docs; from one IP you can have maximum 1k pending connections, and you may need to configure akka.http.host-connection-pool.max-open-requests in your application.conf.

To send a notification message create your notification object, and send it!

Scala
val notification = FcmNotification("Test", "This is a test notification!", Token("token"))
Source.single(notification).runWith(GoogleFcmSink.fireAndForget(fcmConfig))
Full source at GitHub
Java
FcmNotification notification =
    FcmNotification.basic(
        "Test", "This is a test notification!", new FcmNotificationModels.Token("token"));
Source.single(notification)
    .runWith(GoogleFcmSink.fireAndForget(fcmConfig, system, materializer), materializer);
Full source at GitHub

With fire and forget you will just send messages and ignore all the errors. This is not so healthy in general so you can use the send instead.

Scala
val result1: Future[immutable.Seq[FcmFlowModels.FcmResponse]] =
  Source.single(notification).via(GoogleFcmFlow.send(fcmConfig)).runWith(Sink.seq)
Full source at GitHub
Java
CompletionStage<List<FcmFlowModels.FcmResponse>> result1 =
    Source.single(notification)
        .via(GoogleFcmFlow.send(fcmConfig, system, materializer))
        .runWith(Sink.seq(), materializer);
Full source at GitHub

With this type of send you can get responses from the server. These responses can be positive or negative. You can choose what you want to do with this information, but keep in mind if you try to resend the failed messages you will need to implement exponential backoff too!

To help the integration and error handling or logging, there is a variation of the flow where you can send data beside your notification.

Scala
val result2: Future[immutable.Seq[(FcmFlowModels.FcmResponse, String)]] =
  Source.single((notification, "superData")).via(GoogleFcmFlow.sendWithPassThrough(fcmConfig)).runWith(Sink.seq)
Full source at GitHub
Java
CompletionStage<List<Pair<FcmFlowModels.FcmResponse, String>>> result2 =
    Source.single(new Pair<FcmNotification, String>(notification, "superData"))
        .via(GoogleFcmFlow.sendWithPassThrough(fcmConfig, system, materializer))
        .runWith(Sink.seq(), materializer);
Full source at GitHub

Here I send a simple string, but you could use any type.

Scala only

You can build any notification described in the original documentation. It can be done by hand, or using some builder method. If you build your notification from scratch with options (and not with the provided builders), worth to check isSendable before sending.

Scala
val buildedNotification = FcmNotification.empty
  .withTarget(Topic("testers"))
  .withBasicNotification("title", "body")
  //.withAndroidConfig(AndroidConfig(...))
  //.withApnsConfig(ApnsConfig(...))
  .withWebPushConfig(
    WebPushConfig(
      headers = Map.empty,
      data = Map.empty,
      WebPushNotification("web-title", "web-body", "http://example.com/icon.png")
    )
  )
val sendable = buildedNotification.isSendable
Full source at GitHub

There is a condition builder too.

Scala
import akka.stream.alpakka.google.firebase.fcm.FcmNotificationModels.Condition.{Topic => CTopic}
val condition = Condition(CTopic("TopicA") && (CTopic("TopicB") || (CTopic("TopicC") && !CTopic("TopicD"))))
val conditioneddNotification = FcmNotification("Test", "This is a test notification!", condition)
Full source at GitHub

Running the examples

To run the example code you will need to configure a project and notifications in google firebase and provide your own credentials.

The source code for this page can be found here.