Android Notifications using Cordova and Ionic
So you want to add push notifications to your Cordova/Android/Ionic app? I’ll walk through it. Most of this entry applies to Cordova and Android, in case you’ve made the mistake of NOT using Ionic…
Basic Info
Android notifications work through Google GCM (Google Cloud Messaging). You can read the details at http://developer.android.com/google/gcm/index.html, but here’s the summary:
Here’s the narrative version: When your application starts up, the Android device registers with GCM server, which responds with a Registration ID. The device then sends the registration ID to our Message Server, which stores it for future use. Other information, such as the user’s name or location, can be sent for message targeting.
When we want to send out a notification, our Message Server sends the text to the GCM server, along with the registration ID and the API . The GCM server relays info to the device.
The GCM Server
Setting up your Google GCM server should be a fairly quick process. It’s a simple mechanism from our perspective, but does a lot of the heavy lifting for us. Follow these directions to get it running: http://developer.android.com/google/gcm/gs.html. Be sure to make note of your GCM Project ID, and your API Key. The Project ID will go into your Android app configuration, for registration. The API Key is used to send notifications.
The Notification Server
There are any number of ways to do this, and from what I’ve seen, folks often use custom code for this. Of coruse there are some off the shelf products. But for development, I recommend the node-gcm project https://github.com/ToothlessGear/node-gcm. It’s a super-simple way to communicate with the GCM server. First install
npm install node-gcm
Next set up a node script similar to this one:
var gcm = require('node-gcm'); var message = new gcm.Message(); //API Server Key var sender = new gcm.Sender('INSERT_YOUR_API_SENDER_KEY_HERE'); var registrationIds = []; // Value the payload data to send... message.addData('message', "Hello Cordova!"); message.addData('title','Push Notification Sample' ); message.addData('msgcnt','2'); // Shows up in the notification in the status bar message.addData('soundname','beep.wav'); //Sound to play upon notification receipt - put in the www folder in app message.collapseKey = 'demo'; message.delayWhileIdle = true; //Default is false message.timeToLive = 3000;// Duration in seconds to hold in GCM and retry before timing out. Default 4 weeks (2,419,200 seconds) if not specified. // At least one reg id required registrationIds.push('THIS_IS_THE_REGISTRATION_ID_THAT_WAS_GENERATED_BY_GCM'); /** * Parameters: message-literal, registrationIds-array, No. of retries, callback-function */ sender.send(message, registrationIds, 4, function (err, result) { console.log(result); });
The Android App
As you know by now, you need a plugin to do just about anything in Cordova. So install the PushPlugin https://github.com/phonegap-build/PushPlugin. This should be the only plugin you need
for both Android and iOS. You might want to scan the instructions, but (as of now) they are both overly complicated and incomplete. For starters, you don’t need PlugMan to install. Just use:
cordova plugin add https://github.com/phonegap-build/PushPlugin.git
The coding was a little bit tricky, but I finally managed a functional script for Ionic notifications. See inline comments for more information on what’s going on here:
//factory for processing push notifications. angular.module('pushnotification', []) .factory('PushProcessingService', function() { function onDeviceReady() { console.info('NOTIFY Device is ready. Registering with GCM server'); //register with google GCM server var pushNotification = window.plugins.pushNotification; pushNotification.register(gcmSuccessHandler, gcmErrorHandler, {"senderID":gcmAppID,"ecb":"onNotificationGCM"}); } function gcmSuccessHandler(result) { console.info('NOTIFY pushNotification.register succeeded. Result = '+result) } function gcmErrorHandler(error) { console.error('NOTIFY '+error); } return { initialize : function () { console.info('NOTIFY initializing'); document.addEventListener('deviceready', onDeviceReady, false); }, registerID : function (id) { //Insert code here to store the user's ID on your notification server. //You'll probably have a web service (wrapped in an Angular service of course) set up for this. //For example: MyService.registerNotificationID(id).then(function(response){ if (response.data.Result) { console.info('NOTIFY Registration succeeded'); } else { console.error('NOTIFY Registration failed'); } }); }, //unregister can be called from a settings area. unregister : function () { console.info('unregister') var push = window.plugins.pushNotification; if (push) { push.unregister(function () { console.info('unregister success') }); } } } }); // ALL GCM notifications come through here. function onNotificationGCM(e) { console.log('EVENT -> RECEIVED:' + e.event + ''); switch( e.event ) { case 'registered': if ( e.regid.length > 0 ) { console.log('REGISTERED with GCM Server -> REGID:' + e.regid + ""); //call back to web service in Angular. //This works for me because in my code I have a factory called // PushProcessingService with method registerID var elem = angular.element(document.querySelector('[ng-app]')); var injector = elem.injector(); var myService = injector.get('PushProcessingService'); myService.registerID(e.regid); } break; case 'message': // if this flag is set, this notification happened while we were in the foreground. // you might want to play a sound to get the user's attention, throw up a dialog, etc. if (e.foreground) { //we're using the app when a message is received. console.log('--INLINE NOTIFICATION--' + ''); // if the notification contains a soundname, play it. //var my_media = new Media("/android_asset/www/"+e.soundname); //my_media.play(); alert(e.payload.message); } else { // otherwise we were launched because the user touched a notification in the notification tray. if (e.coldstart) console.log('--COLDSTART NOTIFICATION--' + ''); else console.log('--BACKGROUND NOTIFICATION--' + ''); // direct user here: window.location = "#/tab/featured"; } console.log('MESSAGE -> MSG: ' + e.payload.message + ''); console.log('MESSAGE: '+ JSON.stringify(e.payload)); break; case 'error': console.log('ERROR -> MSG:' + e.msg + ''); break; default: console.log('EVENT -> Unknown, an event was received and we do not know what it is'); break; } }
Call it from here:
app.run(function(PushProcessingService) { //run once for the app PushProcessingService.initialize(); });
Hopefully this is helpful to someone!
Thanks to these folks (and others) who put together helpful blogs on this topic:
Hi,
I tried the above and the register function is returning OK as result in gcmSuccessHandler, but I am not able to get the registered ID since the callback function onNotificationGCM is not getting fired.
Any pointers on this please ?
You need to make sure you’re initializing everything properly, so that onNotificationGCM gets registered. First of all, this will not work on a normal browser – you need to run it on a real phone or an emulator (assuming you have it set up properly). Otherwise the plugin will not be accessible (and window.plugins.pushNotification will be undefined).
Amod – I have confirmed that this the case. You can’t do in the Emulator – must be done on the actual device.
Are you able to receive registered id. if yes then let me know how you did it.
Hi – i’m getting exactly the same as Amod. I am using a real device (android phone) and getting ‘OK’ back in gcmSuccessHandler, but onNotificationGCM is never called.
Do you have any further tips on initialising everything properly?
Many thanks…..
Sorted it – i was using the GCM Project ID, but should have been using the Project Number.
You’d have thought the register call would return something other than ‘OK’ wouldn’t you?!
Paul, how you solved issue? I don’t understand what is “GCM Project ID” and “Project Number”? Are you have your project in some repo?
I solved same issue through the following answer.
http://stackoverflow.com/questions/27314422/ngcordova-get-gcm-regid-inside-a-controller
I always get the following:
Uncaught Error: [$injector:unpr] Unknown provider: PushProcessingServiceProvider <- PushProcessingService
When I run:
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services'])
.run(function(PushProcessingService) {
//run once for the app
PushProcessingService.initialize();
});
Do I need include PushProcessingService as a dependency?
Greg
Greg, you do need to include the module containing PushProcessingService as a dependency of your main module. Or just put PushProcessingService into starter.services. This looks like a simple matter of angular not finding the module.
Thanks Mike –
I have placed this in the starter.services file (services.js), but it’s not working. The only way I got it to work is to include as a service in the same app.js file. Any other way to get this work being part of the services.js file (‘starter.services’)
Greg
Are you able to receive registered id. if yes then let me know how you did it.
Hi, im using your example, its clear and i have it almost done. but i have a couple of questions. in the registerID function, do i have to call it on app start? this function autogenerates the registerID? im really confused on this.
Ariel, when you call it from app.run (see above), that’s a point where Cordova is ready but the user hasn’t done anything yet.
The regid is generated by Google. The app receives it in a callback, and then we send it to our message server for storage, which uses it whenever it wants to send a message to this phone.
Hope this helps. There are quite a few moving parts, so this can be confusing at first.
Problem is solved. thankyou for this tutorial
I has implemented as this tut, all most done, GCM return success & message ID, but I can not see notificaton on phone tray when not in foreground
Your code will register with the senderId each time and will get back a registered_id each time. So what is the point in storing the id …just to know when to inform the server of a new registered_id??
Additionally, is it correct to say that there are only two scenarios for your app to receive the event:
1. In foreground
2. User clicks on notification in notifications bar.
I know there is a coldstart vs a non-cold start. However, I was wondering if it is possible to receive the push notification event WHILE the app is still running in the background AND the user has not yet clicked on the notification?
Thanks for your help.
Yes Eric, it registers each time the app starts. That is because we can’t guarantee that the app id will be the same. I believe it changes when the version changes.
Yes, those are the two scenarios for receiving a message. I have not found a need to handle cold start separately. I’m not sure about your last question. If your app is running in the background, there’s no event to handle. It sounds like you want the app to react regardless of whether the user has clicked on the notification. I’m not sure if there’s a way to do that.
I’m not getting the android notifications working when the app is close (background), in foreground the notifications are arriving successfully. Any thoughts about it? Permissions?
By the way, great tutorial !
Thanks for your help
Juan
Hey @Juan , did you find a way to get this working?
Hi,
I has implemented as this tuto and I received this error : “onError – errorId: INVALID_SENDER”.
could you help me ?
I use ionic framework.
Thanks,
Hello, if I use ionic, can i skip some steps?
Hi, Where to add app.run?
Brilliant article. I have just successfully implemented push notifications from Azure’s Mobile Service Schedules. Thank you very much!
Hi Michael, i followed your all instructions, but i have stuck with following error
Uncaught Error: [$injector:unpr] Unknown provider: BenefitsServiceProvider <- BenefitsService <- PushProcessingService
Hey, have you resolve your problem? I have the same…
Just remove “BenefitsService” param from the line :
.factory(‘PushProcessingService’, function(BenefitsService)
and it work. In fact, i have no idea what to do with “BenefitsService”.
Ah, sorry guys. That was a custom service containing business rules from the original implementation. I’ll take it out of the example.
If have tried every single available implementation of Push Notification and have yet to find one that works as advertised. The model is simply too complicated for generalisation.
Well Chris, I agree that this is complicated to implement. But it’s the model that GCM uses. My goal here was to implement it myself without third party tools, and the apps I wired this way have not had any issues. If you want a simpler implementation, you might try going that route, and try something like Parse. There will potentially be a cost involved. Also in the past couple of days Ionic has come up with Ionic Push. Disclaimer: It’s in Alpha and I haven’t tried it yet.
hey Thanks i tried, it is working flawless..BUT i want to know how can i send notification only to select users…something like parsing the message and show notification only when the user is logged in and showing only if the message is for that particular logged in user.
Thanks in advance for the reply
I’m getting error at var pushNotification = window.plugins.pushNotification. It says pushNotification is undefined when I try to register in the next line.
Any idea?
It’s not working for me. I am not able to understand that from where I’ll get registration ID ? Once I get it, I can copy it here “registrationIds.push(‘MY_REG_ID’);”.
But from Where I’ll get it. Please clear my doubt.
hi i have been able to receive push notifications , i have written similar code but i m not getting sound when the push notification is received and one more problem is if i have one push notification and one more notification i send , the second notification is overriding first notification , i need suggestion on how to display multiple notifications and play a sound when notification is received
Hi arijun,
I have the same question. Do you have answer for this question?
Hi Michael,
Thank you for sharing your idea on push notification . I do want know whether application will receive push notification when application is closed !
Also , if its not possible could you recommend any suggestion using cordova plugin to receive notification . I do not want to use third party services .
Yes, this method will work when the app is closed – that’s the whole point! As for a plugin, the Push plugin (which is used here) is the “basic building block” for push notifications. There are also 3rd party controls and hosted services, but those you have to pay for.
That said, the work I did on this was over a year ago, and new options might be available.
Thanks for info Michael,
I am following your approach on implementing push notification since it can be used when application is not running (i.e app is swiped off from mobile).
Could you please share your code on implementing node-gcm service ? I am using ajax to render push notification data and want to pass it to node-gcm .
How could we do this when application is not running ?
Also, I am getting export undefined error. while using node-gcm
Thanks.
Jayesh,
If you want to implement your own server for sending GCM messages,
there are a variety of basic examples available. Here’s one in PHP: https://github.com/mattg888/GCM-PHP-Server-Push-Message. At one point I had a similar system implemented, but no longer have that code. I suggest choosing your favorite server-side technology and looking at what’s on the internet.
Good luck!
Michael,
Thanks a lot for all inputs and help. Currently I am able to send push notification to device when app is closed. :)
Now, I am onto optimization of code .
Thanks.
if I want to use AWS SNS how does the flow work? I shouldn’t push to GCM then, should send the device token to SNS, am I correct?
I do not have quite the same code as you show here but i am using the same plugin in my Ionic app. I have PN working perfectly for all cases in iOS: app closed, foreground and background. But, in Android, I can only get it to work in foreground and background. When the app is closed I get nothing. Is this somehow built in to the plugin (i.e. when app first runs something in plugin registers the app with the Android OS for PN) or is there something in your code which is doing this?
I’ll answer my own question with this post referencing Google’s Android documentation; despite what Michael states above, an Android app is not able to receive push notifications when it is in the “stopped” state (as iOS can).
http://stackoverflow.com/questions/20838415/gcm-push-notification-works-after-app-force-stop