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.
Source: http://bit.ly/1IsmyBr
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;
};
phonegap-plugin-push
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); });
phonegap-plugin-contentsync
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
phonegap-plugin-push
phonegap-plugin-contentsync
Beer?
Push n' Pull
By Simon MacDonald
Push n' Pull
- 13,467