You are on page 1of 58

Android Notifications

Or How I Learned to Stop Worrying and Love


NotificationCompat.Builder
Agenda:
1. How a Push Goes From A to B
2. Building a Notification
3. Troubleshooting Tips
How a push goes from A to B
Google or Firebase Cloud Messaging?

● Firebase is the future


○ Acquired by Google in 2014
○ Has become Google’s mobile developer toolset

● Which should you use?


○ Existing GCM projects will still work
○ New projects must be created using Firebase

● What’s different?
○ Easier integration
○ All new features will be FCM only!
Registration for Push

Play Services
Backend

FirebaseInstanceIdService

Slack API
Slack Android Client
Push flight plan

Slack Firebase Cloud


Message Messaging Server
Server

Google Play Services

Slack Push
Server
FirebaseMessagingService

SlackMessagingService
What’s in a push (Part I: Google Bits)

{
"to":
"e_a8VzsygpY:APA91bGGx4-LyXb1o8YJyO8Vdswf4Z1NGIZwh2hV06
Xx_bx1JCx4Lcd1W-Q8mG6wX8IYLods19BpVfsl23YHoHgqJpMbbPa70
xVZcNdzl0GMzzV4pzOrJe",
"priority": "high",
"data": { … }
}
What’s in a push (Part I: Google Bits)

{
"to":
"e_a8VzsygpY:APA91bGGx4-LyXb1o8YJyO8Vdswf4Z1NGIZwh2hV06
Xx_bx1JCx4Lcd1W-Q8mG6wX8IYLods19BpVfsl23YHoHgqJpMbbPa70
xVZcNdzl0GMzzV4pzOrJe",
"priority": "high",
"data": { … }
}
What’s in a push (Part I: Google Bits)

{
"to":
"e_a8VzsygpY:APA91bGGx4-LyXb1o8YJyO8Vdswf4Z1NGIZwh2hV06
Xx_bx1JCx4Lcd1W-Q8mG6wX8IYLods19BpVfsl23YHoHgqJpMbbPa70
xVZcNdzl0GMzzV4pzOrJe",
"priority": "high",
"data": { … }
}

Everything but the data section is stripped out when


the payload is passed to the application
What’s in a push (Part II: Slack Bits)

"data": {
"sound": "b2.mp3",
"badge": 1,
"payload_version": "3",
"timestamp": "1469635289.000006",
"author_display_name": "Kodos Rigellian",
"author_avatar": "...",
"message": "hi",
"recent_messages": { … }
}
What’s in a push (Part II: Slack Bits)

"data": {
"sound": "b2.mp3",
"badge": 1,
"payload_version": "3",
"timestamp": "1469635289.000006",
"author_display_name": "Kodos Rigellian",
"author_avatar": "...",
"message": "hi",
"recent_messages": { … }
}
Building a Notification
What to build and post with?

● NotificationCompat.Builder
○ Provides setters for each component of the notification
○ Can use anything exposed by it safely regardless of OS version

● NotificationManagerCompat
○ Posts built notifications using
notify(int id, Notification notification)
Basic Notification Example
builder.setContentTitle(title)
.setContentText(text)
.setColor(color)
.setSmallIcon(R.drawable.ic_notification_24dp)
.setLargeIcon(avatarBitmap)
.setWhen(dateTime.getMillis());

notificationManager.notify(id, builder.build());
Basic Notification Example
builder.setContentTitle(title)
.setContentText(text)
.setColor(color)
.setSmallIcon(R.drawable.ic_notification_24dp)
.setLargeIcon(avatarBitmap)
.setWhen(dateTime.getMillis());

notificationManager.notify(id, builder.build());
Basic Notification Example
builder.setContentTitle(title)
.setContentText(text)
.setColor(color)
.setSmallIcon(R.drawable.ic_notification_24dp)
.setLargeIcon(avatarBitmap)
.setWhen(dateTime.getMillis());

notificationManager.notify(id, builder.build());
Basic Notification Example
builder.setContentTitle(title)
.setContentText(text)
.setColor(color)
.setSmallIcon(R.drawable.ic_notification_24dp)
.setLargeIcon(avatarBitmap)
.setWhen(dateTime.getMillis());

notificationManager.notify(id, builder.build());
Basic Notification Example
builder.setContentTitle(title)
.setContentText(text)
.setColor(color)
.setSmallIcon(R.drawable.ic_notification_24dp)
.setLargeIcon(avatarBitmap)
.setWhen(dateTime.getMillis());

notificationManager.notify(id, builder.build());
Basic Notification Example
builder.setContentTitle(title)
.setContentText(text)
.setColor(color)
.setSmallIcon(R.drawable.ic_notification_24dp)
.setLargeIcon(avatarBitmap)
.setWhen(dateTime.getMillis());

notificationManager.notify(id, builder.build());
Basic Notification Example
builder.setContentTitle(title)
.setContentText(text)
.setColor(color)
.setSmallIcon(R.drawable.ic_notification_24dp)
.setLargeIcon(avatarBitmap)
.setWhen(dateTime.getMillis());

notificationManager.notify(id, builder.build());
BigTextStyle
builder.setContentTitle(title)
.setContentText(text)

.setStyle(new NotificationCompat.BigTextStyle()
.bigText(text)
.setSummaryText(account.getTeamName()));
BigTextStyle
builder.setContentTitle(title)
.setContentText(text)

.setStyle(new NotificationCompat.BigTextStyle()
.bigText(text)
.setSummaryText(account.getTeamName()));
BigTextStyle
builder.setContentTitle(title)
.setContentText(text)

.setStyle(new NotificationCompat.BigTextStyle()
.bigText(text)
.setSummaryText(account.getTeamName()));
BigTextStyle
builder.setContentTitle(title)
.setContentText(text)

.setStyle(new NotificationCompat.BigTextStyle()
.bigText(text)
.setSummaryText(account.getTeamName()));
BigTextStyle
builder.setContentTitle(title)
.setContentText(text)

.setStyle(new NotificationCompat.BigTextStyle()
.bigText(text)
.setSummaryText(account.getTeamName()));
MessagingStyle
MessagingStyle messagingStyle = new MessagingStyle("Mike")
.setConversationTitle(contentTitle);

for (Message message : messagesForChannel) {


messagingStyle.addMessage(
message.getMessage(),
message.getTimestamp(),
message.getAuthor());
}

builder.setContentTitle(title)
.setContentText(text)

.setSubText(account.getTeamName())
.setStyle(messagingStyle);
MessagingStyle
MessagingStyle messagingStyle = new MessagingStyle("Mike")
.setConversationTitle(contentTitle);

for (Message message : messagesForChannel) {


messagingStyle.addMessage(
message.getMessage(),
message.getTimestamp(),
message.getAuthor());
}

builder.setContentTitle(title)
.setContentText(text)

.setSubText(account.getTeamName())
.setStyle(messagingStyle);
MessagingStyle
MessagingStyle messagingStyle = new MessagingStyle("Mike")
.setConversationTitle(contentTitle);

for (Message message : messagesForChannel) {


messagingStyle.addMessage(
message.getMessage(),
message.getTimestamp(),
message.getAuthor());
}

builder.setContentTitle(title)
.setContentText(text)

.setSubText(account.getTeamName())
.setStyle(messagingStyle);
MessagingStyle
MessagingStyle messagingStyle = new MessagingStyle("Mike")
.setConversationTitle(contentTitle);

for (Message message : messagesForChannel) {


messagingStyle.addMessage(
message.getMessage(),
message.getTimestamp(),
message.getAuthor());
}

builder.setContentTitle(title)
.setContentText(text)

.setSubText(account.getTeamName())
.setStyle(messagingStyle);
MessagingStyle
MessagingStyle messagingStyle = new MessagingStyle("Mike")
.setConversationTitle(contentTitle);

for (Message message : messagesForChannel) {


messagingStyle.addMessage(
message.getMessage(),
message.getTimestamp(),
message.getAuthor());
}

builder.setContentTitle(title)
.setContentText(text)

.setSubText(account.getTeamName())
.setStyle(messagingStyle);
MessagingStyle
MessagingStyle messagingStyle = new MessagingStyle("Mike")
.setConversationTitle(contentTitle);

for (Message message : messagesForChannel) {


messagingStyle.addMessage(
message.getMessage(),
message.getTimestamp(),
message.getAuthor());
}

builder.setContentTitle(title)
.setContentText(text)

.setSubText(account.getTeamName())
.setStyle(messagingStyle);
Still with us?
Grouping Notifications
● A group consists of:
○ A set of one or more individual notifications
○ A single summary notification
■ Made by calling setGroupSummary(true)

● Groups are keyed by unique strings that you choose

● Android will NOT group your notifications until:


○ A second notification is posted to a specific group
○ A summary notification is posted

● You cannot post a summary in the same notify() call as a


notification!
Grouping Notification Example

id=1000 id=1999
groupKey=”MyGroup” groupKey=”MyGroup”
isGroupSummary=false isGroupSummary=true
text=”My first message” text=”My first message”
Grouping Notification Example

id=1000 id=1999
groupKey=”MyGroup” groupKey=”MyGroup”
isGroupSummary=false isGroupSummary=true
text=”My first message” text=”My first message”

id=1001 id=1999
groupKey=”MyGroup” groupKey=”MyGroup”
isGroupSummary=false isGroupSummary=true
text=”My second message” text=”2 new messages”

Wear + N and up Devices See These Pre-N Devices See The Most
as a Group Recently Posted Summary Only!
Grouping Visualized
Summary on KitKat Grouped on Nougat
Group Summaries Continued
● Build using stored notifications you’ve seen before
○ SharedPref/DB recommended as mentioned earlier

● Order can matter!


○ For best results, post the summary first, then the individual
notification

● Summaries don’t require sound, but set it anyways


○ Wear devices use the summary sound/vibrate data in some cases
Clearing Notifications

● setAutoCancel(true) is almost always the right thing to do!

● Don’t forget to handle the user entering from the launcher icon or
recent app list!
Clearing Notifications

● NotificationManager’s clear(id) can do two things:


○ Clear an individual notification by id
○ Clear an entire group by using the group summary’s notification id

id=1000
clear(1000)
groupKey=”MyGroup” clear(1001)
isGroupSummary=false
text=”My first message”

id=1001 id=1999
groupKey=”MyGroup” groupKey=”MyGroup”
isGroupSummary=false isGroupSummary=true
text=”My second message” text=”2 new messages”
Clearing Notifications

● NotificationManager’s clear(id) can do two things:


○ Clear an individual notification by id
○ Clear an entire group by using the group summary’s notification id

id=1000
clear(1999)
groupKey=”MyGroup”
isGroupSummary=false
text=”My first message”

id=1001 id=1999
groupKey=”MyGroup” groupKey=”MyGroup”
isGroupSummary=false isGroupSummary=true
text=”My second message” text=”2 new messages”
Clearing Notifications

● If you want to nuke the site from orbit...


○ clearAll()
Taking Action

builder
.setContentIntent(userClickPendingIntent)
.setDeleteIntent(dismissPendingIntent)

Don’t forget to set these on both your individual notifications as well as the
group summary if present!
Reply Action
// Remote input instance to fetch data from the user
RemoteInput remoteInput =
new RemoteInput.Builder(KEY_REPLY_TEXT)
.setLabel(replyLabelString)
.setChoices(/* string array */)
.build();

// Create the reply action and add the remote input


builder.addAction(
new NotificationCompat.Action.Builder(
replyDrawable, replyLabelString, pendingIntent)
.addRemoteInput(remoteInput)
.build());

------- Where you handle the pending intent ---------


RemoteInput.getResultsFromIntent(intent)
.getCharSequence(KEY_REPLY_TEXT);
Reply Action
// Remote input instance to fetch data from the user
RemoteInput remoteInput =
new RemoteInput.Builder(KEY_REPLY_TEXT)
.setLabel(replyLabelString)
.setChoices(/* string array */)
.build();

// Create the reply action and add the remote input


builder.addAction(
new NotificationCompat.Action.Builder(
replyDrawable, replyLabelString, pendingIntent)
.addRemoteInput(remoteInput)
.build());

------- Where you handle the pending intent ---------


RemoteInput.getResultsFromIntent(intent)
.getCharSequence(KEY_REPLY_TEXT);
Reply Action
// Remote input instance to fetch data from the user
RemoteInput remoteInput =
new RemoteInput.Builder(KEY_REPLY_TEXT)
.setLabel(replyLabelString)
.setChoices(/* string array */)
.build();

// Create the reply action and add the remote input


builder.addAction(
new NotificationCompat.Action.Builder(
replyDrawable, replyLabelString, pendingIntent)
.addRemoteInput(remoteInput)
.build());

------- Where you handle the pending intent ---------


RemoteInput.getResultsFromIntent(intent)
.getCharSequence(KEY_REPLY_TEXT);
Reply Action
// Remote input instance to fetch data from the user
RemoteInput remoteInput =
new RemoteInput.Builder(KEY_REPLY_TEXT)
.setLabel(replyLabelString)
.setChoices(/* string array */)
.build();

// Create the reply action and add the remote input


builder.addAction(
new NotificationCompat.Action.Builder(
replyDrawable, replyLabelString, pendingIntent)
.addRemoteInput(remoteInput)
.build());

------- Where you handle the pending intent ---------


RemoteInput.getResultsFromIntent(intent)
.getCharSequence(KEY_REPLY_TEXT);
Reply Action
// Remote input instance to fetch data from the user
RemoteInput remoteInput =
new RemoteInput.Builder(KEY_REPLY_TEXT)
.setLabel(replyLabelString)
.setChoices(/* string array */)
.build();

// Create the reply action and add the remote input


builder.addAction(
new NotificationCompat.Action.Builder(
replyDrawable, replyLabelString, pendingIntent)
.addRemoteInput(remoteInput)
.build());

------- Where you handle the pending intent ---------


RemoteInput.getResultsFromIntent(intent)
.getCharSequence(KEY_REPLY_TEXT);
Reply Action
// Remote input instance to fetch data from the user
RemoteInput remoteInput =
new RemoteInput.Builder(KEY_REPLY_TEXT)
.setLabel(replyLabelString)
.setChoices(/* string array */)
.build();

// Create the reply action and add the remote input


builder.addAction(
new NotificationCompat.Action.Builder(
replyDrawable, replyLabelString, pendingIntent)
.addRemoteInput(remoteInput)
.build());

------- Where you handle the pending intent ---------


RemoteInput.getResultsFromIntent(intent)
.getCharSequence(KEY_REPLY_TEXT);
Reply Action
// Remote input instance to fetch data from the user
RemoteInput remoteInput =
new RemoteInput.Builder(KEY_REPLY_TEXT)
.setLabel(replyLabelString)
.setChoices(/* string array */)
.build();

// Create the reply action and add the remote input


builder.addAction(
new NotificationCompat.Action.Builder(
replyDrawable, replyLabelString, pendingIntent)
.addRemoteInput(remoteInput)
.build());

------- Where you handle the pending intent ---------


RemoteInput.getResultsFromIntent(intent)
.getCharSequence(KEY_REPLY_TEXT);
Reply Action
// Remote input instance to fetch data from the user
RemoteInput remoteInput =
new RemoteInput.Builder(KEY_REPLY_TEXT)
.setLabel(replyLabelString)
.setChoices(/* string array */)
.build();

// Create the reply action and add the remote input


builder.addAction(
new NotificationCompat.Action.Builder(
replyDrawable, replyLabelString, pendingIntent)
.addRemoteInput(remoteInput)
.build());

------- Where you handle the pending intent ---------


RemoteInput.getResultsFromIntent(intent)
.getCharSequence(KEY_REPLY_TEXT);
Reply Action
// Remote input instance to fetch data from the user
RemoteInput remoteInput =
new RemoteInput.Builder(KEY_REPLY_TEXT)
.setLabel(replyLabelString)
.setChoices(/* string array */)
.build();

// Create the reply action and add the remote input


builder.addAction(
new NotificationCompat.Action.Builder(
replyDrawable, replyLabelString, pendingIntent)
.addRemoteInput(remoteInput)
.build());

------- Where you handle the pending intent ---------


RemoteInput.getResultsFromIntent(intent)
.getCharSequence(KEY_REPLY_TEXT);
Reply Action
// Remote input instance to fetch data from the user
RemoteInput remoteInput =
new RemoteInput.Builder(KEY_REPLY_TEXT)
.setLabel(replyLabelString)
.setChoices(/* string array */)
.build();

// Create the reply action and add the remote input


builder.addAction(
new NotificationCompat.Action.Builder(
replyDrawable, replyLabelString, pendingIntent)
.addRemoteInput(remoteInput)
.build());

------- Where you handle the pending intent ---------


RemoteInput.getResultsFromIntent(intent)
.getCharSequence(KEY_REPLY_TEXT);
Troubleshooting Tips
Tracking Pushes
● Each push your server sends is assigned a message id from Google
"message_id":"0:1479505763653918%3a63b8c3f9fd7ecd"

● Information to record
○ FCM message id
○ Internal unique id from your backend for this particular push

● Record this information on server and device side to determine if


○ Push was sent and received
○ Push was sent but not received
○ Push was sent and received more than once (Google error)
○ Push was sent twice and received twice (Local push server error)
Build a Silent Test Push System
● Come up with a data payload that indicates this is a test push

data {
isTest: true
id: <server generated id>
}

● Have client ack this push via an API to your backend

● Allows easy silent testing of pushes to confirm FCM link is working


We made it!
Q&A
Thanks!

You might also like