Pro Yearly is on sale from $80 to $50! »

The evolution of Android notification

The evolution of Android notification

Slides from my talk at Android Makers 2017 about notifications

05dd369062f7c4e450e1e08d1471da5b?s=128

Jeremie Martinez

April 11, 2017
Tweet

Transcript

  1. The evolution of notifications @JeremMartinez

  2. What is a notification? 1

  3. None
  4. “A notification is a message you can display to the

    user outside of your application's normal UI. […] Both the notification area and the notification drawer are system-controlled areas that the user can view at any time.” Definition
  5. “A notification is a message you can display to the

    user outside of your application's normal UI. […] Both the notification area and the notification drawer are system-controlled areas that the user can view at any time.” Definition
  6. Awareness I exist!

  7. Quick access and actions!

  8. History 2

  9. None
  10. None
  11. None
  12. 13:37 Content title Content text Gingerbread (2.3)

  13. Content title Content text SubText 13:37 Ice Scream Sandwich (4.0)

  14. Content title Content text SubText Action 13:37 Jelly Bean (4.1|2|3)

  15. Content title Content text SubText Action 13:37 Kitkat (4.4)

  16. Content title Content text SubText 13:37 ACTION Lollipop (5.0)

  17. Content title Content text SubText 13:37 ACTION Marshmallow (6.0)

  18. Content title Content text SubText ACTION 13:37 Android Makers Nougat

    (7.0)
  19. Android

  20. Content title Content text SubText ACTION 13:37 Android Makers

  21. Be a good citizen 3

  22. Letters Telegrams Phone calls SMS Emails Batman

  23. Letters Telegrams Phone calls SMS Emails Batman [SPAM]

  24. 1

  25. Disruptions through the day

  26. None
  27. None
  28. Deletion of information by mistake

  29. Info forgotten Bad timing

  30. 1 Amount Priority Metadata Category

  31. 1 Amount Priority Metadata Category

  32. 1 Amount Priority Metadata Category Importance

  33. Categorize 4

  34. None
  35. FUN PAIN

  36. URGENT NON-URGENT FUN PAIN

  37. URGENT NON-URGENT FUN PAIN

  38. URGENT FUN

  39. VIP of our life Urgent & Fun

  40. URGENT NON-URGENT FUN PAIN

  41. FUN NON-URGENT

  42. Procrastination Non-urgent & Fun

  43. URGENT NON-URGENT FUN PAIN

  44. URGENT PAIN

  45. Livelihood Urgent & Painful

  46. URGENT NON-URGENT FUN PAIN

  47. NON-URGENT PAIN

  48. Nagging Non-urgent & Painful

  49. 2 main principles to evaluate a notification

  50. Be relevant Help yourself with context

  51. Be legitimate Don’t lie about urgency

  52. Use case

  53. Scheduled

  54. Scheduled

  55. Always up to date

  56. Scheduled

  57. Urgent

  58. Triggering 5

  59. Notification notification = new NotificationCompat.Builder(this). setSmallIcon(R.drawable.ic_notification). setContentTitle("Title"). setContentText("Text"). build(); NotificationManagerCompat.from(this).notify(12345,

    notification);
  60. Notification notification = new NotificationCompat.Builder(this). setSmallIcon(R.drawable.ic_notification). setContentTitle("Title"). setContentText("Text"). build(); NotificationManagerCompat.from(this).notify(12345,

    notification);
  61. 1. Local events e.g. downloads, updates, posting a tweet

  62. 2. Scheduled AlarmManager

  63. context.getSystemService(AlarmManager.class);

  64. alarmManager.setExact(type, triggerAtMillis, operation);

  65. alarmManager.setExact(type, triggerAtMillis, operation);

  66. if (BuildUtils.hasMarshmallow()) { alarmManager.setExactAndAllowWhileIdle(type, millis, operation); } else if (BuildUtils.hasKitKat())

    { alarmManager.setExact(type, millis, operation); } else { alarmManager.set(type, millis, operation); } setExact
  67. if (BuildUtils.hasMarshmallow()) { alarmManager.setExactAndAllowWhileIdle(type, millis, operation); } else if (BuildUtils.hasKitKat())

    { alarmManager.setExact(type, millis, operation); } else { alarmManager.set(type, millis, operation); } setExact
  68. if (BuildUtils.hasMarshmallow()) { alarmManager.setExactAndAllowWhileIdle(type, millis, operation); } else if (BuildUtils.hasKitKat())

    { alarmManager.setExact(type, millis, operation); } else { alarmManager.set(type, millis, operation); } setExact
  69. if (BuildUtils.hasMarshmallow()) { alarmManager.setExactAndAllowWhileIdle(type, millis, operation); } else if (BuildUtils.hasKitKat())

    { alarmManager.setExact(type, millis, operation); } else { alarmManager.set(type, millis, operation); } setExact
  70. alarmManager.setExact(type, triggerAtMillis, operation);

  71. alarmManager.setExact( , triggerAtMillis, operation); type

  72. type AlarmManager.RTC System.currentTimeMillis() AlarmManager.RTC_WAKEUP | time since boot including sleep

  73. type AlarmManager.ELAPSED_REALTIME SystemClock.elapsedRealtime() AlarmManager.ELAPSED_REALTIME_WAKEUP | wall clock time in UTC

  74. alarmManager.setExact( , triggerAtMillis, operation); type

  75. alarmManager.setExact( , , operation); type triggerAtMillis

  76. alarmManager.setExact( , , ); type triggerAtMillis operation

  77. operation public static PendingIntent getBroadcast( … ) public static PendingIntent

    getActivity( … ) public static PendingIntent getService( … ) PendingIntent
  78. Is it that simple ?

  79. PendingIntent.getActivity( context, 123, intent, FLAG_UPDATE_CURRENT); final Intent intent = new

    Intent(this, MainActivity.class); PendingIntent.getActivity( context, 123, intent, FLAG_UPDATE_CURRENT); = ?
  80. PendingIntent.getActivity( context, 123, intent, FLAG_UPDATE_CURRENT); final Intent intent = new

    Intent(this, MainActivity.class); PendingIntent.getActivity( context, 123, intent, FLAG_UPDATE_CURRENT);
  81. PendingIntent.getActivity( context, 1234, intent, FLAG_UPDATE_CURRENT); final Intent intent = new

    Intent(this, MainActivity.class); PendingIntent.getActivity( context, 1235, intent, FLAG_UPDATE_CURRENT); = ?
  82. PendingIntent.getActivity( context, 1234, intent, FLAG_UPDATE_CURRENT); final Intent intent = new

    Intent(this, MainActivity.class); PendingIntent.getActivity( context, 1235, intent, FLAG_UPDATE_CURRENT);
  83. PendingIntent.getActivity( context, 123, intent1, FLAG_UPDATE_CURRENT); final Intent intent1 = new

    Intent(this, MainActivity.class); PendingIntent.getActivity( context, 123, intent2, FLAG_UPDATE_CURRENT); = ? final Intent intent2 = new Intent(this, MainActivity.class);
  84. PendingIntent.getActivity( context, 123, intent1, FLAG_UPDATE_CURRENT); PendingIntent.getActivity( context, 123, intent2, FLAG_UPDATE_CURRENT);

    final Intent intent1 = new Intent(this, MainActivity.class); final Intent intent2 = new Intent(this, MainActivity.class);
  85. PendingIntent.getActivity( context, 123, intent1, FLAG_UPDATE_CURRENT); final Intent intent1 = new

    Intent(this, MainActivity.class). putExtra("extra1", "extra1"); PendingIntent.getActivity( context, 123, intent2, FLAG_UPDATE_CURRENT); = ? final Intent intent2 = new Intent(this, MainActivity.class). putExtra(“extra2", "extra2");
  86. PendingIntent.getActivity( context, 123, intent1, FLAG_UPDATE_CURRENT); final Intent intent1 = new

    Intent(this, MainActivity.class). putExtra("extra1", "extra1"); PendingIntent.getActivity( context, 123, intent2, FLAG_UPDATE_CURRENT); final Intent intent2 = new Intent(this, MainActivity.class). putExtra(“extra2", "extra2");
  87. PendingIntent.getActivity( context, 123, intent1, FLAG_UPDATE_CURRENT); final Intent intent1 = new

    Intent(this, MainActivity.class). addCategory(Intent.CATEGORY_APP_BROWSER); PendingIntent.getActivity( context, 123, intent2, FLAG_UPDATE_CURRENT); = ? final Intent intent2 = new Intent(this, MainActivity.class). addCategory(Intent.CATEGORY_APP_CALCULATOR);
  88. PendingIntent.getActivity( context, 123, intent1, FLAG_UPDATE_CURRENT); final Intent intent1 = new

    Intent(this, MainActivity.class). addCategory(Intent.CATEGORY_APP_BROWSER); PendingIntent.getActivity( context, 123, intent2, FLAG_UPDATE_CURRENT); final Intent intent2 = new Intent(this, MainActivity.class). addCategory(Intent.CATEGORY_APP_CALCULATOR);
  89. public boolean filterEquals(Intent other) { if (other == null) {

    return false; } if (!Objects.equals(this.mAction, other.mAction)) return false; if (!Objects.equals(this.mData, other.mData)) return false; if (!Objects.equals(this.mType, other.mType)) return false; if (!Objects.equals(this.mPackage, other.mPackage)) return false; if (!Objects.equals(this.mComponent, other.mComponent)) return false; if (!Objects.equals(this.mCategories, other.mCategories)) return false; return true; }
  90. - Survive app upgrade - Survive reboot How long ?

    Be future proof !
  91. 3. Realtime Push notifications: GCM & Firebase

  92. - Use extensively group - Be careful about marketing abuse

    - Don’t forget to remove notifications
  93. Snooze

  94. Time-out

  95. Styling 6

  96. BigPictureStyle BigTextStyle InboxStyle MediaStyle MessagingStyle Notification.Style

  97. Default

  98. BigText

  99. BigPicture

  100. Media

  101. Notification.Style DecoratedCustomViewStyle

  102. RemoteViews Run in another process

  103. None
  104. Avoid RemoteViews Prefer default styling or span

  105. Spanned everything! Very powerful API — Available everywhere

  106. None
  107. SpannableString delayedSpanned = new SpannableString(delayed); delayedSpanned.setSpan(new StrikethroughSpan(), // 0, delayedSpanned.length(),

    // Spanned.SPAN_INCLUSIVE_INCLUSIVE); delayedSpanned.setSpan(new ForegroundColorSpan(textColor), // 0, delayedSpanned.length(), // Spanned.SPAN_INCLUSIVE_INCLUSIVE);
  108. SpannableString delayedSpanned = new SpannableString(delayed); delayedSpanned.setSpan(new StrikethroughSpan(), // 0, delayedSpanned.length(),

    // Spanned.SPAN_INCLUSIVE_INCLUSIVE); delayedSpanned.setSpan(new ForegroundColorSpan(textColor), // 0, delayedSpanned.length(), // Spanned.SPAN_INCLUSIVE_INCLUSIVE);
  109. SpannableString delayedSpanned = new SpannableString(delayed); delayedSpanned.setSpan(new StrikethroughSpan(), // 0, delayedSpanned.length(),

    // Spanned.SPAN_INCLUSIVE_INCLUSIVE); delayedSpanned.setSpan(new ForegroundColorSpan(textColor), // 0, delayedSpanned.length(), // Spanned.SPAN_INCLUSIVE_INCLUSIVE);
  110. delayedSpanned.setSpan(new ForegroundColorSpan(textColor), // 0, delayedSpanned.length(), // Spanned.SPAN_INCLUSIVE_INCLUSIVE); SpannableString delayedSpanned =

    new SpannableString(delayed); delayedSpanned.setSpan(new StrikethroughSpan(), // 0, delayedSpanned.length(), // Spanned.SPAN_INCLUSIVE_INCLUSIVE); textView.setText(getString(R.string.travelTextWithDelay, // time, // delayedSpanned));
  111. delayedSpanned.setSpan(new ForegroundColorSpan(textColor), // 0, delayedSpanned.length(), // Spanned.SPAN_INCLUSIVE_INCLUSIVE); SpannableString delayedSpanned =

    new SpannableString(delayed); delayedSpanned.setSpan(new StrikethroughSpan(), // 0, delayedSpanned.length(), // Spanned.SPAN_INCLUSIVE_INCLUSIVE); textView.setText(getString(R.string.travelTextWithDelay, // time, // delayedSpanned));
  112. delayedSpanned.setSpan(new ForegroundColorSpan(textColor), // 0, delayedSpanned.length(), // Spanned.SPAN_INCLUSIVE_INCLUSIVE); SpannableString delayedSpanned =

    new SpannableString(delayed); delayedSpanned.setSpan(new StrikethroughSpan(), // 0, delayedSpanned.length(), // Spanned.SPAN_INCLUSIVE_INCLUSIVE); textView.setText(TagFormatter.from(this, R.string.travelTextWithDelay). with("arrival_time", time). with("estimated_arrival_time", delayedSpanned). format()); textView.setText(getString(R.string.travelTextWithDelay, // time, // delayedSpanned));
  113. Other tips 7

  114. Transient Be able to find the same info elsewhere in

    the app
  115. Be patient Wait for the content

  116. Clean up Delete outdated notification

  117. None
  118. Think VIP Help Android to order

  119. None
  120. Think multi-devices Synchronize notifications

  121. Conclusion 8 @JeremMartinez