Notify your users, the right way

Notify your users, the right way

Notifications have been one of the core features in the Android UI since the first public release of the platform.
In this talk, presented at Droidcon Turin 2018, I gave an introduction showing the evolution of notifications in Android and how developers can customise them to give users the best way of accessing important information and perform quick actions at a glance.

D4be3ad792b57408b3ab6fe98caef08e?s=128

danybony

April 19, 2018
Tweet

Transcript

  1. Notify your users the right way Daniele Bonaldo

  2. Daniele Bonaldo Android Developer @danybony_ danybony

  3. A notification is a message that Android displays outside your

    app's UI to provide the user with reminders, communication from other people, or other timely information from your app
  4. Donut (1.6)

  5. Gingerbread (2.3)

  6. Ice Cream Sandwich (4.0)

  7. Jelly Bean (4.1 - 4.2 - 4.3) - Kitkat (4.4)

  8. Wear 1.x

  9. Lollipop (5.0) - Marshmallow (6.0)

  10. Nougat (7.0)

  11. Wear 2.0

  12. Oreo (8.0)

  13. Create a Notification val notification = Notification.Builder(context) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("Title") .setContentText("Content")

    .build() val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(NOTIFICATION_ID, notification)
  14. On Android O…

  15. On Android O… E/NotificationService: No Channel found for pkg=com.google.android.apps.photos, channelId=null…

  16. Channels

  17. Channels val channel = NotificationChannel( CHANNEL_ID, "Status updates", NotificationManager.IMPORTANCE_DEFAULT )

    channel.description = description channel.lightColor = Color.RED val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel)
  18. Channels val channel = NotificationChannel( CHANNEL_ID, "Status updates", NotificationManager.IMPORTANCE_DEFAULT )

    channel.description = description channel.lightColor = Color.RED val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel)
  19. Channels val channel = NotificationChannel( CHANNEL_ID, "Status updates", NotificationManager.IMPORTANCE_DEFAULT )

    channel.description = description channel.lightColor = Color.RED val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel)
  20. Channels val notification = Notification.Builder(context) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("Title") .setContentText("Content") .build()

  21. Channels val notification = Notification.Builder(context) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("Title") .setContentText("Content") .setChannelId(CHANNEL_ID) .build()

  22. Channels val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("Title") .setContentText("Content") .build()

  23. None
  24. None
  25. None
  26. Expandable notifications

  27. Big text style

  28. Big text style val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.new_mail) .setContentTitle(emailSender)

    .setContentText(emailSubject) .setLargeIcon(emailSenderAvatar) .setStyle(NotificationCompat.BigTextStyle() .bigText(emailSubjectAndSnippet)) .build()
  29. Big text style val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.new_mail) .setContentTitle(emailSender)

    .setContentText(emailSubject) .setLargeIcon(emailSenderAvatar) .setStyle(NotificationCompat.BigTextStyle() .bigText(emailSubjectAndSnippet)) .build()
  30. Large image style

  31. Large image style val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.new_image) .setContentTitle(imageTitle)

    .setContentText(imageDescription) .setLargeIcon(largeBitmap) .setStyle(NotificationCompat.BigPictureStyle() .bigPicture(largeBitmap) .bigLargeIcon(null)) .build()
  32. Large image style val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.new_image) .setContentTitle(imageTitle)

    .setContentText(imageDescription) .setLargeIcon(largeBitmap) .setStyle(NotificationCompat.BigPictureStyle() .bigPicture(largeBitmap) .bigLargeIcon(null)) .build()
  33. Inbox style

  34. Inbox style val notification = NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_inbox) .setContentTitle("New mails")

    .setContentText("You have 3 new emails") .setLargeIcon(largeBitmap) .setStyle(NotificationCompat.InboxStyle() .addLine(getSubjectAndMessage(...)) .addLine(getSubjectAndMessage(...)) .addLine(getSubjectAndMessage(...)) ) .build()
  35. Inbox style val notification = NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_inbox) .setContentTitle("New mails")

    .setContentText("You have 3 new emails") .setLargeIcon(largeBitmap) .setStyle(NotificationCompat.InboxStyle() .addLine(getSubjectAndMessage(...)) .addLine(getSubjectAndMessage(...)) .addLine(getSubjectAndMessage(...)) ) .build()
  36. Messaging style

  37. Messaging style val message1 = NotificationCompat.MessagingStyle.Message( messages[0].getText(), messages[0].getTime(), messages[0].getSender() )

    val message2 = NotificationCompat.MessagingStyle.Message( messages[1].getText(), messages[1].getTime(), messages[1].getSender() ) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.new_message) .setStyle(NotificationCompat.MessagingStyle(disaplayedUserName) .addMessage(message1) .addMessage(message2)) .build()
  38. Media style

  39. Media style val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setSmallIcon(R.drawable.ic_stat_player) .addAction(R.drawable.ic_thumb_down,

    "Dislike", thumbDownPendingIntent) .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) .addAction(R.drawable.ic_next, "Next", nextPendingIntent) .addAction(R.drawable.ic_thumb_up, “Like", thumbDownPendingIntent) .setStyle(NotificationCompat.MediaStyle() .setShowActionsInCompactView(1, 2, 3) .setMediaSession(mediaSession.getSessionToken())) .setContentTitle("Wonderful song") .setContentText("Awesome Band") .setLargeIcon(albumArtBitmap) .build()
  40. val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setSmallIcon(R.drawable.ic_stat_player) .addAction(R.drawable.ic_thumb_down, "Dislike", thumbDownPendingIntent)

    .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) .addAction(R.drawable.ic_next, "Next", nextPendingIntent) .addAction(R.drawable.ic_thumb_up, “Like", thumbDownPendingIntent) .setStyle(NotificationCompat.MediaStyle() .setShowActionsInCompactView(1, 2, 3) .setMediaSession(mediaSession.getSessionToken())) .setContentTitle("Wonderful song") .setContentText("Awesome Band") .setLargeIcon(albumArtBitmap) .build() Media style
  41. val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setSmallIcon(R.drawable.ic_stat_player) .addAction(R.drawable.ic_thumb_down, "Dislike", thumbDownPendingIntent)

    .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) .addAction(R.drawable.ic_next, "Next", nextPendingIntent) .addAction(R.drawable.ic_thumb_up, “Like", thumbDownPendingIntent) .setStyle(NotificationCompat.MediaStyle() .setShowActionsInCompactView(1, 2, 3) .setMediaSession(mediaSession.getSessionToken())) .setContentTitle("Wonderful song") .setContentText("Awesome Band") .setLargeIcon(albumArtBitmap) .build() Media style
  42. Remote views

  43. Remote views val remoteViews = RemoteViews(packageName, R.layout.custom_notification) val bigRemoteViews =

    RemoteViews(packageName, R.layout.custom_notification_big) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setStyle(NotificationCompat.DecoratedCustomViewStyle()) .setSmallIcon(R.drawable.ic_notification) .setCustomContentView(remoteViews) .setCustomBigContentView(bigRemoteViews) .build()
  44. Remote views val remoteViews = RemoteViews(packageName, R.layout.custom_notification) val bigRemoteViews =

    RemoteViews(packageName, R.layout.custom_notification_big) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setStyle(NotificationCompat.DecoratedCustomViewStyle()) .setSmallIcon(R.drawable.ic_notification) .setCustomContentView(remoteViews) .setCustomBigContentView(bigRemoteViews) .build()
  45. Spannables val builder = SpannableStringBuilder() val subjectSpannable = SpannableString(subject) val

    boldSpan = StyleSpan(Typeface.BOLD) subjectSpannable.setSpan(boldSpan, 0, subject.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) builder.append(subjectSpannable).append(' ') builder.append(message)
  46. Timeout

  47. Timeout val notification = NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("Title") .setContentText("Notification content")

    .setTimeoutAfter(TimeUnit.MINUTES.toMillis(10)) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .build()
  48. Timeout val notification = NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("Title") .setContentText("Notification content")

    .setTimeoutAfter(TimeUnit.MINUTES.toMillis(10)) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .build()
  49. Actions

  50. Notification tap action val intent = Intent(context, MainActivity::class.java) intent.flags =

    Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK val pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent, 0) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("Title") .setContentText("Content") .setContentIntent(pendingIntent) .setAutoCancel(true) .build()
  51. Notification tap action val intent = Intent(context, MainActivity::class.java) intent.flags =

    Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK val pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent, 0) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("Title") .setContentText("Content") .setContentIntent(pendingIntent) .setAutoCancel(true) .build()
  52. PendingIntent PendingIntent.getActivity(context, REQUEST_CODE, intent, 0) PendingIntent.getService(context, REQUEST_CODE, intent, 0) PendingIntent.getBroadcast(context,

    REQUEST_CODE, intent, 0)
  53. Additional actions

  54. Additional actions val intent = Intent(context, MainActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK

    or Intent.FLAG_ACTIVITY_CLEAR_TASK val pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent, 0) val shareIntent = Intent(context, NotificationBroadcastReceiver::class.java) shareIntent.action = ACTION_SHARE shareIntent.putExtra(EXTRA_NOTIFICATION_ID, NOTIFICATION_ID) val sharePendingIntent = PendingIntent.getBroadcast(context, 0, shareIntent, 0) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("My notification") .setContentText("Hello World!") .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setContentIntent(pendingIntent) .setAutoCancel(true) .addAction(R.drawable.ic_share, "Share", sharePendingIntent) .build()
  55. Additional actions val intent = Intent(context, MainActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK

    or Intent.FLAG_ACTIVITY_CLEAR_TASK val pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent, 0) val shareIntent = Intent(context, NotificationBroadcastReceiver::class.java) shareIntent.action = ACTION_SHARE shareIntent.putExtra(EXTRA_NOTIFICATION_ID, NOTIFICATION_ID) val sharePendingIntent = PendingIntent.getBroadcast(context, 0, shareIntent, 0) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("My notification") .setContentText("Hello World!") .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setContentIntent(pendingIntent) .setAutoCancel(true) .addAction(R.drawable.ic_share, "Share", sharePendingIntent) .build()
  56. Additional actions - dismiss val intent = Intent(context, MainActivity::class.java) intent.flags

    = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK val pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent, 0) val shareIntent = Intent(context, NotificationBroadcastReceiver::class.java) shareIntent.action = ACTION_SHARE shareIntent.putExtra(EXTRA_NOTIFICATION_ID, NOTIFICATION_ID) val sharePendingIntent = PendingIntent.getBroadcast(context, 0, shareIntent, 0) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("My notification") .setContentText("Hello World!") .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setContentIntent(pendingIntent) .setAutoCancel(true) .addAction(R.drawable.ic_share, "Share", sharePendingIntent) .build() override fun onReceive(context: Context, intent: Intent) { val manager = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager val notificationId = intent.getIntExtra(Notification.EXTRA_NOTIFICATION_ID, -1) manager.cancel(notificationId) }
  57. Direct replies

  58. Direct replies val remoteInput = RemoteInput.Builder(KEY_TEXT_REPLY) .setLabel("Your reply") .build()

  59. Direct replies val remoteInput = RemoteInput.Builder(KEY_TEXT_REPLY) .setLabel("Your reply") .build() val

    replyIntent = Intent(context, NotificationBroadcastReceiver::class.java) replyIntent.action = ACTION_REPLY replyIntent.putExtra(EXTRA_NOTIFICATION_ID, NOTIFICATION_ID) val replyPendingIntent = PendingIntent.getBroadcast(context, ACTION_REQUEST_CODE, replyIntent, 0) val action = NotificationCompat.Action.Builder( R.drawable.ic_reply, "Reply", replyPendingIntent) .addRemoteInput(remoteInput) .build()
  60. Direct replies val remoteInput = RemoteInput.Builder(KEY_TEXT_REPLY) .setLabel("Your reply") .build() val

    replyIntent = Intent(context, NotificationBroadcastReceiver::class.java) replyIntent.action = ACTION_REPLY replyIntent.putExtra(EXTRA_NOTIFICATION_ID, NOTIFICATION_ID) val replyPendingIntent = PendingIntent.getBroadcast(context, ACTION_REQUEST_CODE, replyIntent, 0) val action = NotificationCompat.Action.Builder( R.drawable.ic_reply, "Reply", replyPendingIntent) .addRemoteInput(remoteInput) .build() val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("Message from Bob") .setContentText("Hey there, how are you doing?") .addAction(action) .build()
  61. Direct replies private fun getReplyText(intent: Intent): CharSequence? { val remoteInput

    = RemoteInput.getResultsFromIntent(intent) return remoteInput?.getCharSequence(KEY_TEXT_REPLY) }
  62. Wear support

  63. Wear-only actions val actionExtender = NotificationCompat.Action.WearableExtender() .setHintLaunchesActivity(true) .setHintDisplayActionInline(true); val action

    = NotificationCompat.Action.Builder(R.drawable.ic_reply, "Reply", pendingIntent) .addRemoteInput(remoteInput) .extend(actionExtender) .build() val wearableExtender = NotificationCompat.WearableExtender() wearableExtender.addAction(action) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .extend(wearableExtender) .build()
  64. Remote input smart reply val remoteInput = RemoteInput.Builder(KEY_TEXT_REPLY) .setLabel("Your reply")

    .build() val action = NotificationCompat.Action.Builder(R.drawable.ic_reply, "Reply", pendingIntent) .addRemoteInput(remoteInput) .build()
  65. val remoteInput = RemoteInput.Builder(KEY_TEXT_REPLY) .setLabel("Your reply") .setChoices(arrayOf("Yes", "No", "Maybe")) .build()

    val action = NotificationCompat.Action.Builder(R.drawable.ic_reply, "Reply", pendingIntent) .addRemoteInput(remoteInput) .build() Remote input smart reply
  66. val remoteInput = RemoteInput.Builder(KEY_TEXT_REPLY) .setLabel("Your reply") .setChoices(arrayOf("Yes", "No", "Maybe")) .build()

    val action = NotificationCompat.Action.Builder(R.drawable.ic_reply, "Reply", pendingIntent) .addRemoteInput(remoteInput) .setAllowGeneratedReplies(true) .build() Remote input smart reply
  67. Image in MessagingStyle val message = NotificationCompat.MessagingStyle.Message( messages[0].getText(), messages[0].getTime(), messages[0].getSender()

    ) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.new_message .setStyle(NotificationCompat.MessagingStyle(disaplayedUserName) .addMessage(message) .addMessage(message2)) .build()
  68. Image in MessagingStyle val message = NotificationCompat.MessagingStyle.Message( messages[0].getText(), messages[0].getTime(), messages[0].getSender()

    ).setData("image/png", imageUri) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.new_message .setStyle(NotificationCompat.MessagingStyle(disaplayedUserName) .addMessage(message) .addMessage(message2)) .build()
  69. More info Wearable features for Notifications https://developer.android.com/training/wearables/notifications/index.html Notifications Design Guidelines

    https://material.io/guidelines/patterns/notifications.html Notifications overview https://developer.android.com/guide/topics/ui/notifiers/notifications.html
  70. Daniele Bonaldo Thank You! Any questions? @danybony_ danybony