Support multiple Firebase-Messaging-Services in an android app

Anil Krishna
Streamotion Tech Blog
4 min readJul 9, 2021

At Streamotion, we build and support functionalities for applications like Kayo Sports, Binge which has a vast and diverse user base. To drive effective engagement and retention, we needed the right customer engagement platform.

Our team decided to go with two platforms for a multitude of reasons including analytics, push notifications and targeted user campaigning.

  1. Adobe Experience Platform
  2. Braze

Smooth so far.. 😎 So what’s the problem? 🧐

Both Adobe and Braze platforms use FCM (Firebase Cloud Messaging) to send notifications. For more information on the FCM, click here.

So, on a high level, Adobe and Braze SDKs have their own Messaging Services which extends FCM Service to send push notifications to the desired application.

Now, The Tricky Bit — 🙄

The platform’s messaging service which extends FCM-service work perfectly alright on their own. However, they fail each other when they are put together.

Let’s see this in depth -

According to documentations of FirebaseMessagingService we need to register the platform’s messaging service in our AndroidManifest.xml in order for it to receive the remote messages sent from the platform’s dashboards.

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

Since we needed to support two different platforms, at first we tried registering them both as follows:

<!-- ADOBE -->
<service
android:name=".AdobeFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<!-- BRAZE -->
<service
android:name=".AppboyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>

This is when we realised when a remote message is received by the app, the top most declared service always took the precedence over the other service to parse it and failed to serve the required purpose.

What a bummer..🤦‍♂️ So how did we overcome this limitation? 🤔

After some soul searching over the world wide web, we bumped into some similar discussions and articles which gave us a direction to proceed with. We found out that it was possible to support multiple services with the help of a common messaging proxy service.

So, First and Foremost…

We created a common firebase messaging service proxy which acted as a base class for supporting both Adobe and Braze messaging service and registered this as a main messaging service in our AndroidManifest.xml

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

And Secondly..

Inside FirebaseMessagingServiceProxy we handled the logic to determine which platform the message was received from and invoked the appropriate service class to handle it.

/**
* Proxy firebase push message service handler. Checks if the remote
* push message is received from Adobe or Braze and delegates the
* messages to the appropriate messaging service handler.
* We need this proxy service class because both adobe and braze
* extend FirebaseMessagingService for
* cloud messaging solution. If they are declared as two individual
* services in AndroidManifest the first declared service always
* takes the priority over the other making it inaccessible.
*/
class FirebaseMessagingServiceProxy : FirebaseMessagingService() {private val pushMessagingServices: List<FirebaseMessagingService> by lazy {
listOf(
AdobeFirebaseMessagingService(),
AppboyFirebaseMessagingService()
)
.onEach { it.injectContext(this) }
}
/**
* Called if InstanceID token is updated. This may occur if the
* security of the previous token had been compromised. Note
* that this is called when the InstanceID token is initially
* generated so this is where you would retrieve the token.
*/
override fun onNewToken(token: String) {
super.onNewToken(token)
// Send updated token to braze
Appboy.getInstance(this).registerAppboyPushMessages(token)
// Send updated token to adobe
Config.setPushIdentifier(token)
}
/**
* Called when message is received.
* @param remoteMessage Object representing the message received
* from Firebase Cloud Messaging i.e either adobe or braze at
* this point in time. pushMessagingServices[0] --> Adobe
* pushMessagingServices[1] --> Braze
*/
override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
// Check if push was received from Adobe or Braze and invoke
// the appropriate handler
if (remoteMessage.data?.isNotEmpty() == true) {
val data = remoteMessage.data
if (isPushMessageFromAdobe(data)) {
// adobe
pushMessagingServices[0]
.onMessageReceived(remoteMessage)
} else {
// braze
pushMessagingServices[1]
.onMessageReceived(remoteMessage)
}
}
}
/**
* Checks if the remote message is from Adobe, _dId and _mId are
* very specific to Adobe push data pay load.
*/
private fun isPushMessageFromAdobe(data: Map<String, String>):
Boolean {
return data["_dId"] != null || data["_mId"] != null
}
/**
* Context is needed at least to get the NotificationManager and
show the received notification.
*/
private fun <T : Service> T.injectContext(context: T) {
setField("mBase", context)
}
private fun Any.setField(name: String, value: Any): Boolean =
javaClass.findDeclaredField(name)?.let {
try {
it.isAccessible = true
it.set(this, value)
true
} catch (e: NoSuchFieldException) {
false
} catch (e: SecurityException) {
false
}
} ?: false
private fun Class<*>.findDeclaredField(name: String): Field? {
var clazz: Class<*>? = this
do {
try {
return clazz?.getDeclaredField(name)
} catch (e: NoSuchFieldException) {
// no-op
}
clazz = clazz?.superclass
} while (clazz != null)
return null
}
}

VOILA..… 🥳

It all worked like a charm.. So we went one step further and made this as a separate artifactory module to support multiple applications in our fleet.

Thanks for tuning in to this article from the Streamotion Android team! Be sure to follow this publication if you’re interested in future snippets from Kayo, Binge and more!

--

--

Anil Krishna
Streamotion Tech Blog

Android Engineer @ Streamotion Pvt Ltd | FOXTEL Media | Fox Sports