Do you want to use two or more FirebaseMessagingServices at once?

Volodia Chornenkyi
3 min readApr 2, 2019

--

As of April 10, 2018, Google has deprecated GCM. The GCM server and client APIs are deprecated and will be removed as soon as April 11, 2019. (https://developers.google.com/cloud-messaging/android/android-migrate-fcm)

You know about that, right? Hopefully, you already using Firebase Cloud Messaging otherwise you should hurry up and migrate. Actually, it’s quite easy… Till the moment when you have a few push notification providers. That’s exactly what happened to our project and if look into StackOverflow we were not alone.

If you have one FirebaseMessagingServices and want to add one more this article also for you.

I will assume that you know how to integrate FCM into your app. If no, here is the link: https://firebase.google.com/docs/cloud-messaging/android/client

Okay, so as was mentioned before we have few (2 to be precise) push notification providers:

  • UrbanAirship
  • Taplytics

They work perfectly fine on their own but not together. Problem is in the FirebaseMessagingService class from the firebase-messaging dependency. Once you add them both into your AndroidManifest everything will fall apart. You just can’t have multiple of them at once. And there is no such info in documentation (or at least I was not able to find it).

So, how to overcome this limitation? If you have enough time you can change the source code of each library and use self-compiled aar’s which quite time-consuming but works. One of the users on GitHub was able to do this and this was my plan B if no better solution will appear. Grate that I have teammates. The idea of using reflection appear after a short discussion of the problem.

Attention: This solution works for us for now but who knows what will be in the future. Remember that reflection is not good.

Step 1. Find all libraries implementations of FirebaseMessagingService

As we are using UrbanAirship and Taplytics their files are:

  • AirshipFirebaseMessagingService
  • TLFirebaseMessagingService

Step 2. Create your own FirebaseMessagingService

Next step is to create your own subclass which will be in the AndroidManifest file and will delegate all the available methods.

AndroidManifest.xml
<service android:name=".push_notifications.OwnPushService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>

In our case dependency implementations use only two methods of FirebaseMessagingService so it’s enough to describe logic only for them. If your implementations use more of them feel free to use similar logic.

public class OwnPushService extends FirebaseMessagingService {

private List<FirebaseMessagingService> messagingServices = new ArrayList<>(2);

public OwnPushService() {
messagingServices.add(new AirshipFirebaseMessagingService());
messagingServices.add(new TLFirebaseMessagingService());
}

@Override
public void onNewToken(String s) {
delegate(service -> service.onNewToken(s));
}

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
delegate(service -> service.onMessageReceived(remoteMessage));
}

private void delegate(Action<FirebaseMessagingService> action) {
for (FirebaseMessagingService service : messagingServices) {
action.run(service);
}
}
}

Once you run this code you will get a crash as only your service is running. Other two is just instances of the corresponding classes. So, they don’t have Context attached to them.

Step 3. Provide context

Context is needed at least to get the NotificationManager and show the received notification. As services not attached context is null. Then, let’s provide it… with reflection. And if you check class hierarchy of FirebaseMessagingService you can’t find is actually ContextWrapper with mBase field which is used to get NotificationManager.

private void injectContext(FirebaseMessagingService service) {
setField(service, "mBase", this);
}
private boolean setField(Object targetObject, String fieldName, Object fieldValue) {
Field field;
try {
field = targetObject.getClass().getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
field = null;
}
Class superClass = targetObject.getClass().getSuperclass();
while (field == null && superClass != null) {
try {
field = superClass.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
superClass = superClass.getSuperclass();
}
}
if (field == null) {
return false;
}
field.setAccessible(true);
try {
field.set(targetObject, fieldValue);
return true;
} catch (IllegalAccessException e) {
return false;
}
}

And now both of the push providers work.

Source code: https://gist.github.com/volodia-chornenkyy/0613727510ba88da80465cf5440522b6

Warning

Starting in Android 9 (API level 28), the platform restricts which non-SDK interfaces your app can use. Accessing hidden field mBase of ContextWrapper is one of them which is in the “greylist” so you will get only warning in the console. I tested this solution with the latest (for April 1) build of Android Q on emulator and everything works. More about restrictions you can read by this link.

If anyone will find a better solution please let me know.

--

--