Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Set Course for Notifications… Engage! (Toronto Android Developers Meetup)

877215a85ea128b67b4334142a6df260?s=47 Eric Fung
September 14, 2016

Set Course for Notifications… Engage! (Toronto Android Developers Meetup)

With only a minimum of coding, Firebase provides your Android app with simple push notifications and collects analytics to measure user engagement. However, if you need to customize the notifications or their behavior, you need to implement everything yourself. For a recent Android news app I developed, I wanted to use push messaging to inform users when new articles were published. This talk will describe how I used three Firebase tools (Notifications, Cloud Messaging, and Analytics) to implement custom notifications and analytics to track their performance.

Presented at Toronto Android Developers Meetup (https://www.meetup.com/ToAndroidDev/events/233664313/).

Video available at https://www.youtube.com/watch?v=7btudq2PCas

877215a85ea128b67b4334142a6df260?s=128

Eric Fung

September 14, 2016
Tweet

More Decks by Eric Fung

Other Decks in Programming

Transcript

  1. Set Course for Notifications… Engage! Eric Fung <efung@acm.org> Toronto Android

    Developers Meetup • 2016-09-14
  2. Entrepreneur's Digest

  3. Let's Try Firebase

  4. None
  5. Cloud Messaging vs Notifications

  6. Cloud Messaging • Service that reliably delivers messages • Optional

    application server integration • Multiplatform client SDKs
  7. Notifications • Graphical console • Built on top of FCM

    • Designed for marketing and engagement • Built-in analytics
  8. Android Integration compile "com.google.firebase:firebase-messaging:${FIREBASE_VERSION}"

  9. None
  10. Android client SDK • Permissions added to manifest • Creating

    registration token, and refresh • Broadcast receiver to listen for notifications • Display notification if in background • Topic management • Analytics
  11. Notification Console

  12. Funnel Analysis

  13. Great! But… • Different notification style? • Grouping and summary?

    • Background send-to-sync? • Analytics and reporting?
  14. Notification Messages { "to" : "/topics/new_articles", "notification" : { "body"

    : "How to Grow a Business with Email Marketing", "title" : "New article available", "icon" : "myicon" } } • Predefined keys • Displayed automatically if backgrounded • Console only sends this kind
  15. Data Messages { "to" : "/topics/new_articles", "data" : { "object_type"

    : "article", "status" : "new", "item_id" : 14142135 } } • Freeform keys and values • App decides what to do • Console cannot send this kind
  16. Notification vs Data

  17. Data Messages • Completely handled by app • So no

    automatic analytics ! • And no funnel analysis ! !
  18. Is it possible to get the best of both worlds?

  19. Grouping Notifications

  20. Grouping Notifications

  21. Grouping Notifications NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setContentTitle("New article published")

    .setContentText("Time Management Tips") .setSmallIcon(R.drawable.ic_stat_notify) .setDeleteIntent(getDeleteIntent(article._id())) .setContentIntent(getContentIntent(article._id())) .setGroup(GROUP_NEW_ARTICLE) // ⾢⾢⾢⾢⾢ ;
  22. Summary Notification NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setContentTitle(res.getQuantityString(R.plurals.summary_title, numActive, numActive))

    .setSmallIcon(R.drawable.ic_stat_notify) .setStyle(inboxStyle) .setGroup(GROUP_NEW_ARTICLE) // ⾢⾢⾢⾢⾢ .setGroupSummary(true) // ⾢⾢⾢⾢⾢ ;
  23. Stacked Notifications in Wear

  24. Bundled Notifications in Nougat

  25. Showing Notifications

  26. Reasons to Defer Display • User configurable "do not disturb"

    time • Send-to-sync messages
  27. Being a Good App Citizen • Messages delivered by FCM

    are nearly instantaneous • If devices all sync at the same time, server may be overwhelmed • Clients should jitter the start of their sync • Conserve battery by batching requests that require network
  28. Scheduling Background Sync • JobScheduler API has been ported to

    older platforms • Firebase JobDispatcher • Recommended for new apps • Source available, but no release • GCM Network Manager • Uses Play Services • No new development
  29. Scheduling Background Sync • Jobs described with requirements for network

    and timing • Scheduler tries to batch and defer as long as possible • See https://developer.android.com/topic/performance/ scheduling.html
  30. build.gradle compile "com.google.android.gms:play-services-gcm:${PLAY_SERVICES_VERSION}" • Network manager code is in GCM

    client library • Can be used with FCM
  31. AndroidManifest.xml <service android:name=".notifications.MyFirebaseMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service>

    • Need to override the default behaviour in the FirebaseMessagingService class • This is where the onMessageReceived() callback lives
  32. AndroidManifest.xml <service android:name=".service.SyncBackgroundService" android:exported="true" android:permission="com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE"> <intent-filter> <action android:name="com.google.android.gms.gcm.ACTION_TASK_READY" /> </intent-filter>

    </service> • Define subclass of GcmTaskService
  33. SyncBackgroundService.java @Override public void onMessageReceived(RemoteMessage remoteMessage) { Map<String, String> data

    = remoteMessage.getData(); long articleId = parseIdFromData(data); // Remember that we want to show notification for articleId syncHelper.scheduleArticleFetch(rng.nextInt(SYNC_MIN) + 1, SYNC_MAX); }
  34. scheduleArticleFetch(min, max) OneoffTask task = new OneoffTask.Builder() .setService(SyncBackgroundService.class) .setExtras(taskExtras) .setRequiredNetwork(Task.NETWORK_STATE_CONNECTED)

    .setExecutionWindow(min, max) .setUpdateCurrent(true) .build(); gcmNetworkManager.schedule(task);
  35. MyGcmTaskService.java @Override public int onRunTask(TaskParams taskParams) { Bundle extras =

    taskParams.getExtras(); … api.fetchLatest(…); // Alert observers that sync has completed, e.g. via event bus }
  36. Recording Analytics

  37. Recording Analytics • Automatically collected events use reserved names •

    Our events will have to use different names • We will also try to create our own funnels
  38. Detect Foreground or Background • Typically, do something if in

    foreground, otherwise post notification • Ordered Broadcast https://commonsware.com/blog/2010/08/11/activity- notification-ordered-broadcast.html • Application.onTrimMemory() and ActivityLifecycleCallbacks: http:// www.developerphil.com/no-you-can-not-override-the- home-button-but-you-dont-have-to/
  39. notification_receive, notification_foreground @Override public void onMessageReceived(RemoteMessage remoteMessage) { … Bundle

    params = …; boolean isBackground = ((MyApp)getApplication()).isBackground(); firebaseAnalytics.logEvent(isBackground ? "notif_receive" : "notif_foreground", params);
  40. notification_receive, notification_foreground @Override public void onMessageReceived(RemoteMessage remoteMessage) { … Bundle

    params = …; // Optional: Put item ID and other attributes into params boolean isBackground = ((MyApp)getApplication()).isBackground(); firebaseAnalytics.logEvent(isBackground ? "notif_receive" : "notif_foreground", params);
  41. Opening Notification final int flags = PendingIntent.FLAG_UPDATE_CURRENT; Intent serviceIntent =

    new Intent(NotificationActionService.ACTION_TAPPED, null, context, NotificationActionService.class); serviceIntent.putExtra(NotificationActionService.EXTRA_ITEM_ID, itemId); Intent intent = PendingIntent.getService(context.getApplicationContext(), REQUEST_CODE_TAPPED, serviceIntent, flags); NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setContentIntent(intent) …
  42. Opening Notification final int flags = PendingIntent.FLAG_UPDATE_CURRENT; Intent serviceIntent =

    new Intent(NotificationActionService.ACTION_TAPPED, // ⾢⾢⾢⾢⾢ null, context, NotificationActionService.class); serviceIntent.putExtra(NotificationActionService.EXTRA_ITEM_ID, itemId); Intent intent = PendingIntent.getService(context.getApplicationContext(), REQUEST_CODE_TAPPED, serviceIntent, flags); NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setContentIntent(intent) …
  43. NotificationActionService.java public class NotificationActionService extends IntentService { @Override protected void

    onHandleIntent(Intent intent) { … switch (intent.getAction()) { case ACTION_TAPPED: … firebaseAnalytics.logEvent("notif_open", params); } }
  44. Dismissing Notification • Similar handling for setDeleteIntent when notification is

    swiped • Record analytics for notif_dismissed event
  45. User Properties

  46. None
  47. User Properties • Attributes that describe user, recorded on every

    event • Some already collected automatically • Need to register properties in Firebase Console • For notifications: • Whether they are enabled for the app • Whether they are blocked • Any related experiment variants
  48. Notification Settings • Make sure you have a Settings activity

    to enable notifications • Defined with android.intent.category.NOTIFICATION_PREFERENCES • When value changed, log user property
  49. Notification Settings @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {

    … boolean notifsEnabled = sharedPreferences.getBoolean("pref_key_notifs", false); firebaseAnalytics.setUserProperty("app_notifs_enabled", Boolean.toString(notifsEnabled)) }
  50. None
  51. System Notification Settings • boolean notifManagerCompat.areNotificationsEnabled() (Support Library 24.0.0+) •

    ⾠ If older than API 19, will always return true • Record as user property firebaseAnalytics.setUserProperty("system_notifs_enabled", nmc.areNotificationsEnabled())
  52. Audiences

  53. Audiences • Can only filter on one property at a

    time • Audiences segment user base by combining events, parameters and properties
  54. Notification Funnel Analysis • Filter by audience (optional) • Define

    two events, e.g. notif_receive, notif_open • Not the same as the funnel analysis in Firebase Notifications though…
  55. Analytics Limitations • Event parameters aren't available for reporting •

    Parameters can only be specified in Audiences • For funnel analysis, we can't limit events to a specific parameter
  56. None
  57. Improving Analytics • Feature request that Firebase engineers know about

    • (And hopefully working on) • Can also stream data to BigQuery for deep analysis
  58. Sending Notifications to FCM

  59. None
  60. Sending Notifications to FCM • Don't need application server •

    Use topic messaging • All clients subscribed to one topic firebase.subscribeToTopic("all_new_articles");
  61. Sending Notifications to FCM Example using cURL BODY=$(cat <<END {

    "data": { "key1": "val1", "key2": "val2" }, "to" : "/topics/all_new_articles", } END ) curl \ --header "Authorization: key=$FCM_SERVER_KEY" \ --header "Content-Type: application/json" \ https://fcm.googleapis.com/fcm/send \ --data-ascii "${BODY}"
  62. Wrap Up

  63. Wrap Up • Firebase Notifications give you a basic implementation

    • For more complex needs, get to know Firebase Cloud Messaging • Firebase didn't currently save me any code • While you can get similar analytics, it's not as easy to do
  64. The End