Simon MacDonald

Push

What is a Push?

Push notifications let your application notify a user of new messages or events even when the user is not actively using your application.

Prevalence of Push

Analysis of the Android app store shows that 14.90% of all apps include push. 

However, 42.06% of installed apps include push.

Opt in Rates

Industry wide opt in rates were at 42% in 2014. 

This is down from 45% in 2013.

Opt in rates tend to be higher on Android than iOS. 

How to increase opt in Rates?

Provide your own UI to explain how push notifications will be used in your app. Then have the system prompt for permission

This can boost opt in rates up to 60%

Don't rely on the operating system to prompt your users.

Push can backfire

Guess what is the top reason why people uninstall apps?

Yup, annoying push notifications!

How not to annoy people?

Don't send too many push notifications. Social apps can get away with sending more push notifications than a promotional app can.

Make it useful

  • Don't assume one push makes sense for all of your users.
  • Broadcast pushes are opened about 3% of the time.
  • Targeted pushes are opened about 7% of the time.
  • Use geofencing to push targeted info.

Make it configurable

Make it as easy as possible for your users to configure which types of push notifications they want to receive.

Good

Better

Quiet Hours

Because push messages are generally paired with sound or phone vibrations, timing should never be overlooked. You don’t want to send a push notification to your app users at 3am and unleash the wrath of customers whose sleep has been disturbed or worse yet, the wrath of their significant other.

Don't make the assumption your users will schedule Do not disturb on iOS or setup Android's Priority Mode.

Registration

App

Push Service

App Server

register()

registration ID

save registration ID

register()

Android

pushNotification.register(
    successHandler,
    errorHandler, {
    "senderID":"sender_id",
    "ecb":"onNotificationGCM"
});

iOS

if ( device.platform == 'android' ){






} else if ( device.platform == 'ios' ) {
  pushNotification.register(
      tokenHandler,
      errorHandler, {
        "badge":"true",
        "sound":"true",
        "alert":"true",
        "ecb":"onNotificationAPN"
    });
}

Windows

 else if (device.platform == 'windows') {
  pushNotification.register(
      channelHandler,
      errorHandler, {
        "channelName":"channel_id",
        "ecb":"onNotificationWindows"
    });
}

What did you notice about that code sample?

  • Configuration dependent upon device platform.
  • Different methods for receiving notifications.
  • Different methods for receiving registration ID's.

Receive

Registration ID

Android

function onNotificationGCM(e) {
  switch( e.event ) {
    case 'registered':
      console.log("regID = " + e.regid);
    break;
  }
}

iOS

Windows

function tokenHandler (result) {
    console.log('regID = ' + result);
}
function channelHandler(result) {
    console.log('regID = ' + result.uri);
}

Hmmmm....

  • Each registration handler get the registration ID in a slightly different format.

Android: result.regid

iOS: result

Windows: result.uri

That reminds me of...

a code smell is a surface indication that usually corresponds to a deeper problem in the system 

Martin Fowler, 1999

So we set upon unifying this interface

Android

var push = PushNotification.init({
   "android": {
     "senderId": "sender_id"
   },
   "ios": {
     "badge":"true",
     "sound":"true",
     "alert":"true"
   },
   "wp": {
     "channelName": "channel_id"
   }
});

 

push.on('registration', function(data) {
  console.log('regID = ' 
    + data.registrationId);
});

iOS

Windows

Now...

  • No more need to check what device type you are on. Just pass your config data to init and it will figure it out for you.
  • Only one method is required to receive the registration ID for all platforms.
  • The object that contains your device's registration ID is consistent across all platforms.
  • But where did the notification handler go?

Good Question

Let's talk about...

Notifications

App

Push Service

App Server

send push

send push

Android

iOS

Windows

function onNotificationGCM(e) {
  switch( e.event ) {
    case 'message':
      navigator.notification.alert(
         e.message,null,e.title);
      break;
    case 'registered':
      console.log("regID = " + e.regid);
      break;
  }
}
function onNotificationAPN(e) {
  navigator.notification.alert(
         e.alert,null,e.title);
}
function onNotificationWindows(e) {
  navigator.notification.alert(
         e.Message,null,e.Title);
}

Not as bad as registration but...

  • Android needs a switch statement to handle notification.
  • Each notification has a slightly different data format.
  • It gets worse when you think about sounds and badges.

Android

push.on('notification', function(data) {
  navigator.notification.alert(
     data.message,null,data.title);
});

iOS

Windows

Consistent Notification Object

Same data structure across all platforms.

 

interface Notification {
    String title;
    String message;
    String sound;
    Integer count;
    JsonObject additionalData;
};

Next Steps

  • Continued maintenance
  • Engage 3rd party push vendors to add compatibility
  • Provide feedback on W3C Push API Editor Draft
  • Iterate...

Pull

What is Pull?

Pull is when an application requests, downloads and utilizes new data.

Anatomy of a Pull

App

App Server

XHR Request

XHR Response

Download Request 1

Download Response 1

...

Download Request n

Download Response n

Make a XHR Request

var xhr = new XMLHttpRequest();
xhr.onload = function() {

























Parse the response

Make additional network requests for more content 


  for (var i=0; i < books.length; i++) {
    var ft = new FileTransfer();
    ft.download(
      books[i].cover_image,
      fileURL,
      function(entry) {
        console.log("download complete");
      },
      function(error) {
        console.log(error.source);
      },
      false, {});
  }

 

var books = 
    JSON.parse(this.responseText);
};
xhr.open("get", "books.json", true);
xhr.send();

 

Not bad

Network Requests Total File Size
11 336 kb

but for one request for new content we've made:

we can do a lot better if we zip up all the content into a single file.

App

App Server

Download Request 1

Download Response 1

Download your zipped content

Unzip the response

var ft = new FileTransfer();
ft.download(
  'https://myserver.com/books.zip',
  fileURL,
  function(entry) {
    console.log("download complete");











  },
  function(error) {
    console.log(error.source);
  },
  false, {});
    zip.unzip(    
      entry.toUrl(), 
      destinationDir, 
      function(result) {
        console.log("unzip complete");
      },
      function(progressEvent) {
        console.log(progressEvent.loaded
          / progressEvent.total);
      }
    );

Getting better

Network Requests Total File Size

11

1

336 kb

241 kb

but why am I still not happy?

It's the W3C File API

It's overly complicated to use and both the FileTransfer and Unzip plugins require you to use it in order for you to specify destinations.

As well, the Unzip plugin doesn't have support for Windows Phone. 

Callback Hell anyone?

var fileURL;
window.requestFileSystem(
  LocalFileSystem.PERSISTENT, 0, 
  function(fs) {
    fs.root.getFile("tempFile.zip", 
      {create: true, exclusive: false},
      function​(entry) {
        fileURL = entry.toURL();
      }, 
      function(error) {
        console.log(error.code);
    });
  }, 
  function(error) {
    console.log(error.code);
});

Just call sync

var sync = ContentSync.sync({
   src: 'http://myserver.com/books.zip',
   id: 'books'
});

sync.on('progress', function(data) {
    console.log(data.progress);
});

sync.on('complete', function(data) {
    console.log('Sync complete');
    console.log(data.localPath);
});

What just happened there?

  • Your zip file was downloaded to a temp location.
  • It was extracted to a private location that only your app can access.
  • You get progress events every step of the way.
  • A path was returned to you to let you know where your content is stored.
  • Oh, and that zip file was cleaned up.
  •  
var sync = ContentSync.sync({
   src: 'http://myserver.com/books.zip',
   id: 'books',
   type: 'replace'
});

sync.on('complete', function(data) {
    console.log('Sync complete');
    console.log(data.localPath);
});

replace

  • Default behaviour
  • Deletes any existing content at ID
  • Replaces it with new data from the server
var sync = ContentSync.sync({
   src: 'http://myserver.com/books.zip',
   id: 'books',
   type: 'local'
});

sync.on('complete', function(data) {
    console.log('Sync complete');
    console.log(data.localPath);
});

local

  • If the content already exists for the given ID it skips the download and unzip phase and just returns the localPath.
  • If the content doesn't already exist it acts just like replace.
var sync = ContentSync.sync({
   src: 'http://myserver.com/books.zip',
   id: 'books',
   type: 'merge'
});

sync.on('complete', function(data) {
    console.log('Sync complete');
    console.log(data.localPath);
});

merge

  • Doesn't remove content at ID.
  • Download and unzip's the new content.
  • Overlays the new content over top of existing content.

That's pretty good

It gets better!

var sync = ContentSync.sync({
   src: 'http://myserver.com/books.zip',
   id: 'books',
   type: 'merge',
   copyCordovaAssets: true
});

sync.on('complete', function(data) {
    console.log('Sync complete');
    console.log(data.localPath);
});

copyCordovaAssets

  • This operation is the last phase before the sync completes.
  • If true it copies the cordova.js and all the plugin JS to your target ID.
  • Then you can run your app from the ID location.

Wait did he just imply I can update my app over the air without going through app store approval?

Yes

, yes I did.

3.3.2 An Application may not download or install executable code. Interpreted code may only be used in an Application if all scripts, code and interpreters are packaged in the Application and not downloaded. The only exception to the foregoing is scripts and code downloaded and run by Apple's built-in WebKit framework, provided that such scripts and code do not change the primary purpose of the Application by providing features or functionality that are inconsistent with the intended and advertised purpose of the Application as submitted to the App Store.

Yes, Apple Allows Updating

These apps are already using content sync

Push

Pull