Notification / Notification.Builder / Nougat

Nougat – Messaging Style Notifications

Nougat has now been released in to the wild and there are a number of new features which developers should be considering. In this loosely-related series of articles we’re going to be looking at various aspects of these new features to see how we can best make use of them. In this article we’re going to look at another new Notification style introduced in Nougat.

nougat-smallPreviously we looked at Bundled Notifications which provide a mechanism to send lots of Notification without simply overwhelming the user, but there is another new Notification style introduced by Nougat which is particularly we suited to our pseudo-messaging app – co-incidentally named Messaging Style Notifications. Messaging-tyle Notifications are specifically designed for messaging apps and provides a conversation-like view.

The big difference with Messaging-style notifications compared to Bundled Notifications is that with Bundled Notifications we could keep creating new Notifications and they would be grouped together by the Notification framework. However with Messaging-style notifications we only have a single Notification and we add all of our messages to that. That poses something of a problem with our pseudo-messaging app because we aren’t currently persisting our messages, so we’ll need to do that. I won’t do a deep-dive in to how that is implemented – I’ll just give a quick overview, but all of the changes for this are in a single commit in the source so anyone interested can take a look at that.

I’m using Ryan Harter’s AutoValue Gson extension to persist a message List to and from JSON and storing this in SharedPreferences (within Device encrypted storage). The main are of interest to us is how we do this in our NotificationBuilder:

final class NotificationBuilder {
    private static final String GROUP_KEY = "Messenger";
    private static final String MESSAGES_KEY = "Messages";
    private static final String NOTIFICATION_ID = "com.stylingandroid.nougat.NOTIFICATION_ID";
    private static final int SUMMARY_ID = 0;
    private static final String EMPTY_MESSAGE_STRING = "[]";

    private final Context context;
    private final NotificationManagerCompat notificationManager;
    private final SharedPreferences sharedPreferences;
    private final MessageListMarshaller marshaller;

    static NotificationBuilder newInstance(Context context) {
        Context appContext = context.getApplicationContext();
        Context safeContext = ContextCompat.createDeviceProtectedStorageContext(appContext);
        if (safeContext == null) {
            safeContext = appContext;
        }
        NotificationManagerCompat notificationManager = NotificationManagerCompat.from(safeContext);
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(safeContext);
        MessageListMarshaller marshaller = MessageListMarshaller.newInstance();
        return new NotificationBuilder(safeContext, notificationManager, sharedPreferences, marshaller);
    }

    private NotificationBuilder(Context context,
                                NotificationManagerCompat notificationManager,
                                SharedPreferences sharedPreferences,
                                MessageListMarshaller marshaller) {
        this.context = context.getApplicationContext();
        this.notificationManager = notificationManager;
        this.sharedPreferences = sharedPreferences;
        this.marshaller = marshaller;
    }
    .
    .
    .
    @NonNull
    private List<Message> addMessage(Message newMessage) {
        List<Message> messages = getMessages();
        messages.add(newMessage);
        saveMessages(messages);
        return messages;
    }

    private void saveMessages(List<Message> messages) {
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString(MESSAGES_KEY, marshaller.encode(messages));
        editor.apply();
    }

    private List<Message> getMessages() {
        String messagesString = sharedPreferences.getString(MESSAGES_KEY, EMPTY_MESSAGE_STRING);
        return marshaller.decode(messagesString);
    }
    .
    .
    .
}

Whenever we add a new message, the current list of messages will be retrieved from SharedPreferences, the new message will be added, and it will be saved back to SharedPreferences.

So with the persistence of our message list in place let’s take a look at how we actually build the notification:

final class NotificationBuilder {
    .
    .
    .
    private static final String MY_DISPLAY_NAME = "Me";
    .
    .
    .
    void sendMessagingStyleNotification(Message newMessage) {
        List messages = addMessage(newMessage);
        updateMessagingStyleNotification(messages);
    }

    private void updateMessagingStyleNotification(List messages) {
        NotificationCompat.MessagingStyle messagingStyle = buildMessageList(messages);
        Notification notification = new NotificationCompat.Builder(context)
                .setStyle(messagingStyle)
                .setSmallIcon(R.drawable.ic_message)
                .build();
        notificationManager.notify(SUMMARY_ID, notification);
    }

    private NotificationCompat.MessagingStyle buildMessageList(List messages) {
        NotificationCompat.MessagingStyle messagingStyle =
                new NotificationCompat.MessagingStyle(MY_DISPLAY_NAME)
                        .setConversationTitle("Messenger");
        for (Message message : messages) {
            String sender = message.sender().equals(MY_DISPLAY_NAME) ? null : message.sender();
            messagingStyle.addMessage(message.message(), message.timestamp(), sender);
        }
        return messagingStyle;
    }
    .
    .
    .
}

Looking at the updateMessagingStyleNotification() method the only thing which is unfamiliar is the NotificationCompat.MessagingStyle – which is the new NotificationCompat.Style implementation for the Messaging-style Notifications. This is actually constructed in buildMessageList().

First we construct the MessagingStyle giving both the display name of the current user (the reason for this will become more apparent later on in this series) and a title for the conversation. Next we add each of the messages from our message list. The main thing worthy of note is that we enforce a null sender if the the sender name matches the current user. The reason for this is that the MessagingStyle will display messages from the current user slightly differently and the current user is denoted by a null sender. Once again, this will become clearer later on.

That’s pretty much it. We just need to change our MessengerService to use the appropriate method in NotificationBuilder:

public class MessengerService extends GcmTaskService {
    .
    .
    .
    @Override
    public int onRunTask(TaskParams taskParams) {
        Message message = messenger.generateNewMessage();
        Log.d(TAG, message.toString());
        //notificationBuilder.sendBundledNotification(message);
        notificationBuilder.sendMessagingStyleNotification(message);
        serviceScheduler.scheduleService();
        return GcmNetworkManager.RESULT_SUCCESS;
    }
    .
    .
    .
}

So if we run this we can see that the Notification framework nicely formats this for us if we run this on a Nougat device:

messaging-style-cropped

But what about a pre-Nougat device, what is the user experience in that case:

messaging-style-legacy-cropped

Believe it or not there are actually multiple messages being added to that Notification, but none are being displayed. So there is no backward-compatible rendering on earlier devices, so if you use Messaging-Style for Nougat devices you’ll need to also have a fallback for older devices which are not supported.

In the final article in this series we’ll take a look at another new feature of Nougat Notifications and that is the ability to reply to a message within the Notification itself.

The source code for this article is available here.

© 2016, Mark Allison. All rights reserved.

Copyright © 2016 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

1 Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.