Android SDK

Make it work for all Android versions

A device displaying a new notification with a contact name on the left, a photo, and a text message

Recently, I had to work on a messaging app and I struggled to implement notifications with a contact image on the left, like any other messaging apps do, for Android versions starting from Android 11 (API 30).

A lot of documentation on this subject is not up to date.

When I read an updated one, it talked about using Shortcut API to get a contact image on the left, so I thought it was not what I looked for, but spoil: it was!

In this article, we will look at all the steps to follow to implement a messaging-style notification to get the following result:

Notification with a contact image on the left, a contact name, and a text

Before looking at the specific code to display a notification the way we want, we will sum up the basic way to display a notification.

For anyone having in mind the basic code to do that, you can jump to the next section.

To make our life easier, we will add the dependency on the AndroidX SDK Core that provides Compat classes and methods to avoid adding Android version checks in several places in our code.

We will display a notification with the following configuration:

  • A title: The first line of the notification, displayed in bold. This information is optional.
  • A text: The second line of the notification. The displayed string can take 2 lines before being truncated. This information is optional.
  • A colored small icon. A small icon is mandatory otherwise your app will crash with an IllegalArgumentException: Invalid notification (no valid small icon).
  • AutoCancel set to true will make the notification automatically disappear after the user has clicked on it.
  • Setting a category allows the system to determine if the notification can be displayed when the device is in Do Not Disturb. Here is the documentation.

We will get the following result:

A basic notification with a title, a description, and a Bugdroid head small icon

Here is the code to build the notification:

  1. Starting from Android O (API 26), a notification must be linked to a notification channel to be displayed. It’s out of the scope of this article but you can find the createNotificationChannelIfNeeded implementation here and read the Android documentation here.
  2. You can find the implementation of the createPendingIntent here.
  3. We use the Builder of the NotificationCompat class to build our notification. In the builder’s constructor, we have to pass the channel id used during the construction of the channel, in the createNotificationChannelIfNeeded method.
  4. To display the notification, we have to call the notify method of NotificationManagerCompat and pass the id we want to give to the notification. This id can be lately used to dismiss the notification or update it by notifying a new notification with the same id.

Now that we know how to display a notification, we will get to the main point of this article: display a notification with a contact icon on the left.

To build the notification we want, we have to set a NotificationCompat.Style to the notification by calling NotificationCompat.Builder.setStyle.

There are several styles (ex: BigPictureStyle to add a large image, BigTextStyle to add a large block of text, etc…). In our case, we want to show a conversation.

To do that, we have to instantiate a NotificationCompat.MessagingStyle. The constructor is MessagingStyle(Person user).

Let’s see what a is Person.

Create a Person

The Person API is a simple API used to represent an entity. This entity can contain some information describing it. In our case, we set 2 information:

We will use the compat version of the androidx.core:core SDK. The API has been introduced in Android API 28 and by using the compat version, it will manage Android version checks for us.

To get a Person instance, we have to use the Person.Builder class.

In this example, we get the user profile image in the Bitmap format. We use the IconCompat.createWithBitmap method to get an icon to match the Person API.

In the context of a 1:1 conversation, the name and the icon used are linked to the other person, but in a conversation between several persons, a Person can represent the conversation channel which can have a custom name and custom icon.

Create a MessagingStyle

Now that we have a Person instance, we can create a NotificationCompat.MessagingStyle instance.

We have to add one or more Messages to our style to set the notification’s text.

The constructor of this Message class takes:

  • The text to display
  • The timestamp when the notification is displayed
  • A Person instance representing the message’s sender. In the case of a 1:1 conversation, you use the same Person instance that in the MessagingStyle constructor, otherwise you create a Person instance representing the message’s sender.

To add the Message to the style, we call addMessage.

Now that we have our style, we just have to set it to the NotificationCompat.Builder and display the notification.

By applying a style, data set by calling setContentTitle and setContentText are now ignored so we don’t need to call them anymore.

By executing this code on a device with API < 30, we got the expected notification UI, but on API ≥ 30, the result is the following:

When the notification is collapsed, we don’t see the user’s photo.
The user photo is only displayed when the notification is expanded.

To get good rendering for API ≥ 30, we will have to implement dynamic shortcuts!

At first glance, there is no link between displaying a user photo on the left of a notification and a dynamic shortcut, but starting from API 30, a notification has to be “linked”, by a common ID, to a shortcut to get the notification UI we want.

What is a Shortcut

App’s shortcuts are accessible after a long click on the app icon:

A dynamic shortcut setup for a Person named “User name” with a “Bugdroid” as a contact photo

There are 2 types of shortcuts:

  • Static shortcuts allow to open a specific screen of the app without any personalized information based on the usage of the app. The Intent used to open a screen will not contain custom data. For instance, you can open a camera screen or settings screen but not a conversation screen with a specific person. You can learn how to build a static shortcut here.
  • Dynamic shortcuts can be added and removed according to the app’s usage. The Intent used to open a screen can contain custom data to adapt the screen to a specific usage. For instance, you can open a conversation screen with a specific person by passing the “conversation id” that the screen should display.

Let’s create a dynamic shortcut

In our case, we will create a dynamic shortcut. Each time we need to display a notification, we will create a new shortcut. This is the required way to get the notification UI we want.

Both our dynamic shortcut and our notification will have to share a common ID to “create the bridge” between them and satisfy the system to get the good UI.

Let’s see how to create the shortcut:

  1. Create an Intent that will start your application and open the screen of the conversation linked to the received message.
  2. As for the notification, we will use a “compat” version of the API used to create a shortcut.
  3. Use ShortcutInfoCompat.Builder to create a shortcut. You have to pass a string “shortcutId” that will be used in the notification creation process. We will see how later.
  4. Call setLongLived is optional. If you set it to true, it allows you to keep a shortcut pinned on the launcher even if it has been unpublished by the app. Pinned means placed on the launcher, like an app icon, to quickly access it. This case can occur when you reach the limit of 10 dynamic shortcuts and your shortcut is replaced by a new one.
  5. Call setIntent to set the intent you created earlier.
  6. Call setShortLabel. It will be the name of the shortcut. In our case, we can use the name of the person in the conversation. The recommended maximum length is 10 characters.
  7. Set the same Person used in the MessagingStyle creation by calling setPerson.
  8. If you set a picture to the Person instance, you can set it to the shortcut too by calling setIcon.
  9. Finally, you call build to get a ShortcutInfoCompat instance.
  10. The last step is to publish the shortcut. To do that, we call the static method ShortcutManagerCompat.pushDynamicShortcut(Context, ShortcutInfoCompat). This method, only available in the “compat” version of ShortcutManager, manages a lot of boilerplate code for us to easily publish our shortcut.

Indeed, in ShortcutManager, there are only methods to add and remove shortcuts. This causes a problem because there is a limit to the number of dynamic shortcuts that can exist at a time. If you try to add a new one when the maximum number of shortcuts is reached, an exception is thrown. We would have to code the logic to check if the number is reached, get the list of existing shortcuts, find the oldest, and remove it. By using pushDynamicShortcut, everything is done for us!

Link the notification to the shortcut

During the notification creation, you have to call a new method setShortcutId, and pass the same ID used during the shortcut creation.

Now that the link is done, your notification will have the expected UI even for API ≥ 30! 🎉

In the context of messaging notifications, to provide a good experience to users, we want to avoid displaying one notification per message. A better experience would be to display a unique notification containing all messages from a contact.

MessagingStyle allows that with the method addMessage, which can be called several times on the same style. Each call will add a new text and all texts will be displayed in a chronological order i.e. the oldest first, the newest last.

It’s also possible to retrieve the current active notification (“active” means visible to the user) and extract the MessagingStyle from it. With that, we can add a new message’s text and republish the notification to update it.

Get the current active notification

The first step is to get the current active notification:

We call getActiveNotifications() which returns all visible notifications that have been published by the current application. It returns a list of StatusBarNotification.

On a StatusBarNotification, we can call getId() to get the ID used to publish the notification. Like that, we can find the notification we are interested in by checking the ID.

To get the Notification object, we call getNotification().

Retrieve the MessagingStyle from the notification and add the new message

Now that we have our Notification object, we want to extract its style. We use the static method NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification.

In this example, we retrieve the previous MessagingStyle containing one or already several Messages.

We create a new Message and we add it to the MessagingStyle.

Create or update a notification

Depending on whether there is already an active notification or not, we have to add our message to the existing MessagingStyle or create one and add our Message to it.

After that, you create a new Notification, you apply MessagingStyle to it and you publish it with the ID of the previous notification to update it if there was already one.

Source link