Android Notifications — An elegant way to build and display

Almost all of the Android apps I’ve been working on were commercial apps, and almost all of them are using the push notifications capabilities. Push notifications are a nice way to keep your users engaged, and from the business perspective, taking the advantages they provide is crucial. Of course, there are situations when push notifications doesn’t apply or make sense, but we’re not gonna talk about that now.

Displaying notifications on Android is fairly easy. The documentation is very nice and clean, and describes the best practices and requirements to make the UX compliant to the standards. Nova days, the standard way to send a push to a device is by using the Firebase. If there are no customizations or custom payload, the effort on the Android side in oder to display a notification, branded specifically for the app is matter of defining some properties (like icon etc) in xml. However, this post is about receiving push notifications with custom payload, and display different notification based on that payload. The way I solve this I find pretty handy and I think it deserves to be shared, so other people may find it useful too, or they may get inspired to write a better one.

Let’s dig into the code

First of all, we have to define the dependencies for google play services and Firebase. It’s very well explained here what are the dependencies and their latest versions, so we will skip that step and start with defining services in our AndroidManifest.xml file:

The first one is the one in charge of receiving the events when Firebase is refreshing the device token, and the second one is about receiving the push notifications coming from Firebase. When Firebase refreshes the token, we have to send it to our backend so it gets updated and the device will keep receiving pushes. The implementation is rather pretty straight forward:

And the second service is the one that receives the push notifications. All it does is just deliver the received data into the PushNotification class that knows how to display a notification. Here is how it looks like:

The interesting part

It’s important to mention that the data that is received in the remote message is a Map, and one of key-value pairs inside is an id. This id is very important, because by using that id we will distinguish different types of push notifications, and ultimately we will display different notification. Let’s take a look at the implementation of the PushNotification class:

To start of, let’s get through the class dependencies:

  • NotificationManager is the Android’s SDK notification manager that is used for showing the notifications by it’s notify() method.
  • NotificationItemResolver is an interface that provides a contract for resolving the received data into a type that will be used to build a displayable notification
  • NotificationBuilder is an interface that provides a contract for making android.app.Notification out of the type that was resolved by the resolver.

And here is how they are constructed

Since the NotificationManager is pretty straight forward, let’s take a look at the NotificationItemResolver and it’s concrete implementation:

The implementation of the interface is basically a factory that produces different types of PushNotificationItem based on the id value that comes from Firebase that we discussed before. When we would like to add new type, we will have to add new implementation of PushNotificationItem (which we will check in a moment) and define the mapping in the resolve() method. The PushNotificationItem is an interface that defines the basics of the notification that will be displayed:

When targeting O, the API requires us to provide a channel in order to display a notification. The channel is sort of a group, and the user could enable/disable different channels. Therefore, the channel has an id and a title, and this is what it looks like:

Next, the PushNotificationItem interface defines title, message, smallIcon, and pendingIntent. When the notification will be displayed, this are the items that are going to be displayed. A notification can have many other different things for content, but for this example we take only the basics. Also, a very important thing is the PendingIntent which describes what is going to be opened in the app upon click on the notification.

So far, we have defined two PushNotificationItem types that are resolved in our PushNotificationItemResolver implementation, mainly the PushNotificationItem.Empty, and FriendSuggestionNotification. Let’s get through the second one:

The empty one is quite same, just with different values correspondingly. And it just creates pending intent that opens the app’s main screen.

So far we’ve seen the mechanism behind mapping and preparing the notification data based on the received data from Firebase. We’ve seen that adding new type is as easy as defining a new class that derives from the PushNotificationItem and defining it in the resolver. If it has to go to a new channel that doesn’t exist, we will also have to define new channel class that derives from PushNotificationChannel and that’s it.

Now, let’s take a look at the definition and the implementation of the NotificationBuilder that actually creates a notification that would be actually displayed:

As we may see, the implementation here is fairly simple. It just maps the values from the PushNotificationItem into a android.app.Notification using the NotificationCompat.Builder

Getting back to the show() method of the PushNotification class where we started off, it does the things pretty straight forward: first maps the data into a PushNotificationItem, and if the current build version is O, it also creates a channel. Then it triggers a notify() on the manager with the android.app.Notification that got built out of the PushNotificationItem

As we’ve seen so far, this solution is quite well separated and easy to deal with. It’s easy to provide translation for the notification texts, as all that has to be done is defining translated strings.xml without a need to do any changes on the notifications themselves. Defining a new type of notification requires the backend to send different id for that type, and a new class for it on the Android side. Changing the way the data is mapped, or extending the functionality is as easy as replacing the implementations of the interfaces. And ultimately, and very importantly — this solution is testable. It’s easy to write unit tests for the way the NotificationItemResolver does resolve the data and if it does properly, as well as writing tests for the actual PushNotificationItem types, and check if they return the desired values.

The screencast

Recently, I’ve recorded a screencast where I am demonstrating a work-through handling push notifications, so you may find it helpful to get the whole idea:

Wrap up

That’s it, people, I hope (and I’d be very glad) if somebody finds this useful and handy, and of course, I’m ready to get into some more explanations or general discussions if required.