$30 off During Our Annual Pro Sale. View Details »

The evolution of Android notification

The evolution of Android notification

Slides from my talk at Android Makers 2017 about notifications

Jeremie Martinez

April 11, 2017
Tweet

More Decks by Jeremie Martinez

Other Decks in Technology

Transcript

  1. The evolution of notifications
    @JeremMartinez

    View Slide

  2. What is a
    notification?
    1

    View Slide

  3. View Slide

  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

    View Slide

  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

    View Slide

  6. Awareness
    I exist!

    View Slide

  7. Quick access
    and actions!

    View Slide

  8. History
    2

    View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. 13:37
    Content title
    Content text
    Gingerbread (2.3)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  19. Android

    View Slide

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

    View Slide

  21. Be a good
    citizen
    3

    View Slide

  22. Letters
    Telegrams
    Phone calls
    SMS
    Emails
    Batman

    View Slide

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

    View Slide

  24. 1

    View Slide

  25. Disruptions
    through the day

    View Slide

  26. View Slide

  27. View Slide

  28. Deletion
    of information by mistake

    View Slide

  29. Info forgotten
    Bad timing

    View Slide

  30. 1
    Amount
    Priority
    Metadata
    Category

    View Slide

  31. 1
    Amount
    Priority
    Metadata
    Category

    View Slide

  32. 1
    Amount
    Priority
    Metadata
    Category
    Importance

    View Slide

  33. Categorize
    4

    View Slide

  34. View Slide

  35. FUN
    PAIN

    View Slide

  36. URGENT
    NON-URGENT
    FUN
    PAIN

    View Slide

  37. URGENT
    NON-URGENT
    FUN
    PAIN

    View Slide

  38. URGENT
    FUN

    View Slide

  39. VIP of our life
    Urgent & Fun

    View Slide

  40. URGENT
    NON-URGENT
    FUN
    PAIN

    View Slide

  41. FUN
    NON-URGENT

    View Slide

  42. Procrastination
    Non-urgent & Fun

    View Slide

  43. URGENT
    NON-URGENT
    FUN
    PAIN

    View Slide

  44. URGENT
    PAIN

    View Slide

  45. Livelihood
    Urgent & Painful

    View Slide

  46. URGENT
    NON-URGENT
    FUN
    PAIN

    View Slide

  47. NON-URGENT
    PAIN

    View Slide

  48. Nagging
    Non-urgent & Painful

    View Slide

  49. 2 main principles
    to evaluate a notification

    View Slide

  50. Be relevant
    Help yourself with context

    View Slide

  51. Be legitimate
    Don’t lie about urgency

    View Slide

  52. Use case

    View Slide

  53. Scheduled

    View Slide

  54. Scheduled

    View Slide

  55. Always up to date

    View Slide

  56. Scheduled

    View Slide

  57. Urgent

    View Slide

  58. Triggering
    5

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  62. 2. Scheduled
    AlarmManager

    View Slide

  63. context.getSystemService(AlarmManager.class);

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  78. Is it that simple ?

    View Slide

  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);
    = ?

    View Slide

  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);

    View Slide

  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);
    = ?

    View Slide

  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);

    View Slide

  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);

    View Slide

  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);

    View Slide

  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");

    View Slide

  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");

    View Slide

  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);

    View Slide

  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);

    View Slide

  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;
    }

    View Slide

  90. - Survive app upgrade
    - Survive reboot
    How long ?
    Be future proof !

    View Slide

  91. 3. Realtime
    Push notifications: GCM & Firebase

    View Slide

  92. - Use extensively group
    - Be careful about marketing abuse
    - Don’t forget to remove notifications

    View Slide

  93. Snooze

    View Slide

  94. Time-out

    View Slide

  95. Styling
    6

    View Slide

  96. BigPictureStyle BigTextStyle
    InboxStyle
    MediaStyle
    MessagingStyle
    Notification.Style

    View Slide

  97. Default

    View Slide

  98. BigText

    View Slide

  99. BigPicture

    View Slide

  100. Media

    View Slide

  101. Notification.Style
    DecoratedCustomViewStyle

    View Slide

  102. RemoteViews
    Run in another process

    View Slide

  103. View Slide

  104. Avoid RemoteViews
    Prefer default styling or span

    View Slide

  105. Spanned everything!
    Very powerful API — Available everywhere

    View Slide

  106. View Slide

  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);

    View Slide

  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);

    View Slide

  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);

    View Slide

  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));

    View Slide

  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));

    View Slide

  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));

    View Slide

  113. Other tips
    7

    View Slide

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

    View Slide

  115. Be patient
    Wait for the content

    View Slide

  116. Clean up
    Delete outdated notification

    View Slide

  117. View Slide

  118. Think VIP
    Help Android to order

    View Slide

  119. View Slide

  120. Think multi-devices
    Synchronize notifications

    View Slide

  121. Conclusion
    8
    @JeremMartinez

    View Slide