A good way to handle incoming notifications in Android

How can we process a notification received from Google Cloud Messaging (CGM) in an effective and safe way? Notifications in Android is something that you probably work with if you are an Android Developer, and Android Docs provides the information about how to create a new notification and notify the status bar, but it's time to talk about how these messages can be processed in your application.

Before we get started

In this article I will not show how to build customized notifications, so I imagine that you already know how to do that. Instead, we will discuss a pattern that will help us to manage when a notification needs to be show in status bar and when we process internally (when the application is running and focused). And it will be a lot easier to create and process internal notifications too, when the source is not GCM but instead some condition in app that dispatch the notification, like a synch finished or a message received from some hardware.


The process

The idea is to use a BroadcastReceiver to emit an event to the application and configure a default BroadcastReceiver to receive non-handled messages. Why BroadcastReceiver and not EventBus, Otto or RxJava? I'll talk about it later =)

The overall process is:

  • The application receives a message from GCM (Or another provider — i.e. AeroGear, Parse or the application itself) and delegates to a IntentService
  • This IntentService will broadcast a new event through the application using a ordered broadcast.
  • If some activity or fragment is registered to receive the event, there is two things that can be done: Process the message and do nothing (this will show the notification on status bar) or process the message and abort the broadcast (this will not show the notification on status bar).
  • If no current activity or fragment is registered, or the application is not currently running, a default receiver will take care of the message and display it in the status bar.

Here is the diagram that I made to try explain this:

Some possibilities to work with this strategy

It's normal to see applications that keep some Service alive to listen to this new notifications and process it without showing on status bar using a Binder or registering callbacks. This is valid, but is one more Service running in the user device. In worst cases developers tries to check is the application is running as condition to show or not show the notification, using static variables and so on...

What this approach tries to solve

  1. Process the notification inside the current screen without showing the notification on status bar. This can cause a duplicate and unnecessary information for the user.
  2. Don't need a Service running all the time or when the App is running.
  3. Show a notification on status bar depending on the current screen. This is the result of the ability to register the receiver in some parts of the app when it's convenient**.
  4. Guarantee that your application will always process the notifications from CGM correctly.
  5. Don't need to worry if the app is running or not when a new notification from GCM arrives.
  6. Change the provider easily from GCM or another provider without much pain.

Well, nothing better than code! So let's start with the tech details about how to implement this.

** Let’s say that you develop a SMS app and you need to update the UI in a screen that lists the SMSs when a new notification arrives, and also you don’t need to show the notification on the status bar. If the user is currently in another screen, for example a Settings screen, you want to show the notification on the status bar and ignore the message because it’s not related to the current screen.


SHOW ME THE CODE!

First, we will create the GcmBroadcastReceiver.java, that will be the entry point from a notification coming from GCM.

And configure it to AndroidManifest.xml as usual:

This following code works with Google Play Services — GCM < 8.3.0.

If you are working with the newest version of Google Play Services — GCM >= 8.3.0, the configuration change as following:

The GcmBroadcastReceiver will be a service that extends the GcmListenerService (I will keep the same name as declared in the previous version, even not being a BroadcastService but actually a Service).

And the AndroidManifest in Play-Services >= 8.3.0 is something like this:

The GcmBroadcastReceiver has only one objective: Receive the notification from GCM and start a new IntentService called PushReceiverIntentService, which will process the message. We can skip the use for PushReceiverIntentService, but using this approach we can easily change the provider with minimum effort, just replacing the GcmService to another class depending on your provider.

The second step is create the PushReceiverIntentService to handle this call:

I use in this example a Notification object:

Now, in the PushReceiverIntentService the magic happens: We are broadcasting a ordered event, using the sendOrderedBroadcast method. This event will be delivered in receivers registered in order of priority, so we can set which listeners will receive this event first. This sendOrderedBroadcast will emit the intent with the extras containing a Notification object and two methods: One for process the error delivering the Intent (In case of no one register to receive the event), and another for the completion, just to log or save something if you want to.

About EventBus, Otto, RxJava and related:

Using the sendOrderedBroadcast by the Context instead of LocalBroadcastManager, EventBus or RxJava, we guarantee that the app will receive the notification even if it was started by another process, like a SyncAdapter running in a different process than the application is. This cannot be done using another Bus related system. Also, we can wake up the application using a default broadcast receiver.

Now let's create the default receiver in case that nobody process the notification. To do this we need to create a BroadcastReceiver.

And register in AndroidManifest.xml with zero priority (to be the last one to receive the broadcast).

The zero priority is the main point: Any part of the application can receive the notification broadcast before this receiver just setting the priority to some value greater than zero. It also can abort the broadcast to not show the notification on status bar or leave it to this receiver show the notification.

Just like the GcmBroadcastReceiver, we just start a new IntentService:

The PushIntentService is the class that actually shows the notification on the status bar. So, if this message came to this class, it will be show always.

Until now, we have the first case in the diagram completed:

Processing push with the code written until now

Now it's time to receive the notification in some Activity and don't show the message in status bar, but take some action on UI.

We can make this by registering the broadcast receiver with a higher priority (anything greater than 0). This Activity is a example of how to proceed:

The priority registration is set at line 18. With the priority 1 this method will be called before the PushReceiver and we can stop the event propagation by calling abortBroadcast when the event is received. Check the line 9 in the notificationReceiver to see how it works.

Done! Now we can easily set when some activity or fragment will process the push coming from the cloud and when it will be canceled. If we want to process the push but still show the notification on the status bar, we simple remove the abortBroadcast() call and let the PushReceiver do his work.

Finally, the second flow is completed:

The complete flow from receiving the message to CGM until the last receiver

Usage without GCM

At this point we can dispatch internal notifications like that ones that we receive from GCM. We just need to start PushReceiverIntentService passing the arguments for starting the broadcast, and the application will receive the message normally. I used this in a project that I receive alerts from a hardware connected in the same wifi of the device and has a socket connected though a Service. On each alert message received from the socket, I start the PushReceiverIntentService and let the application handle this by showing a AppCompat's AlertDialog (if the app is running) or show the notification on the status bar (when the app is closed).

In the example available at github, I’m actually doing this to skip the CGM configuration or the need to have a server to send messages to the application. This is how we can start the process internally:

With this call, the IntentService processes the message like being from CGM. Indeed, this make it a lot easier to switch the provider to another service, like Parse or AeroGear.

Conclusion

This pattern could be a lot of classes, receivers and intent for just process a simple push notification from the cloud, and could be a overkill in very small projects. But in large projects or projects that have a large dependency of push notifications, like realtime apps, this could save you a lot of work and made everything more organized because you can set the application behavior of incoming messages based on the current screen. Also, you always have the default push processor to show every non-handled event.

I'm using this approach in several apps until now, and it works very well. Some examples include: A realtime app (highly depending on push notifications), a hardware-communication app that receive alerts and notifications from the physical appliance, and a few "normal" apps that just receive push notifications from new content available. I hope that this article helps you to implement a safe way to process push notifications.

The complete source code was written in Kotlin and is available on github. Take a deep look inside the sample app.

If you have a suggestion, comments or something like that, I'm happy to discuss with you! Thank you for reading.