In App Notifications with the Ionic Framework

I’m working on an app that relies heavily on the concept of in app notifications. Rather than make this app two or more times (in Java for Android, in Objective-C or Swift for iOS, in whatever Windows phones use), I have opted to go hybrid, enabling me to code in JavaScript and deploy anywhere.

Now, all this is tickety boo, except that as you might expect, working in one language for three platforms has its *ahem* caveats.

Before I go further I firstly want to say how amazingly awesome it is that the Ionic Framework even exists, let alone as an open source project that’s free to use for all. I also want to give my love and thanks to all the contributors of plugins that allow Ionic-based apps to do 90%+ of what can be done natively in app.

I can only imagine how difficult it must be to make one plugin that works on multiple platforms, in a market that is as fast paced as the mobile device industry.

Inevitably, I have hit on a few issues as I’ve been working on this app, and a large part of the reason and process behind this is to show what those problems are, and just as importantly, how I have overcome them.

Local Notifications with $cordovaLocalNotification

Sebastián Katzer
Sebastián Katzer

As mentioned, a large part of my app relies on the use of in app notifications.

The gist of the app is that a user can select one or more ‘hours’ to subscribe too / be notified about, and then the mobile device should send an alert when those hours are about to begin.

The obvious starting point here then is the Local Notifications plugin by Sebastián Katzer.

The code itself to get this working is pretty straightforward. This is in large part to the brilliant Wiki documentation that is chock full of helpful examples, coupled nicely with the official documentation – scroll to the bottom of this page for the Ionic-specific Examples section.

So now we have some code to work with:

module.controller('MyCtrl',
  ['$scope', '$rootScope', '$ionicPlatform', '$cordovaLocalNotification',
   function($scope, $rootScope, $ionicPlatform, $cordovaLocalNotification) {

  $ionicPlatform.ready(function () {

    // ========== Scheduling

    $scope.scheduleSingleNotification = function () {
      $cordovaLocalNotification.schedule({
        id: 1,
        title: 'Title here',
        text: 'Text here',
        data: {
          customProperty: 'custom value'
        }
      }).then(function (result) {
        // ...
      });
    };

 

This is where things started to get interesting for me, and also where things started to go wrong.

My first issue was that whilst all the examples I could find involve $scope, I didn’t have access to this as I was calling the schedule method from a Service.

My solution to this (which may be incorrect, definitely feels incorrect, and is not taking advantage of dependency injection so is hard to test), was to switch to the following:

'use strict';

angular.module('core')
    .service('NotificationUpdater', ['$ionicPlatform',function($ionicPlatform) {

        this.updateNotifications = function (chosenHours) {

            $ionicPlatform.ready(function () {
                cordova.plugins.notification.local.cancelAll(); // left this in for show, but not there in the real code

                var now = new Date().getTime(),
                    _5_sec_from_now = new Date(now + 5 * 1000);

                cordova.plugins.notification.local.schedule({
                    title: "Tweet Hours are Starting",
                    text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In nec magna libero. Ut at massa pellentesque massa euismod volutpat id quis metus.",
                    at: _5_sec_from_now,
                    badge: 3
                });
            })
        };

 

This is a rough draft / proof of concept. But, importantly, it worked. And I say it worked, it took a day and a half of hacking to get it to work. It may not be pretty but by Jove it sends me some damn alerts and right now, that’s good enough.

If Only Coding Were That Easy

Of course, if it had been that simple, it wouldn’t be worth blogging about.

I strongly encourage you to make use of a README.md file in your project, making notes on how you get things to work in your project. It’s all fine and dandy today, but what happens next week, next month, or in my case, an entire year later you decide to pick the project back up and things don’t work as you remember. You may be thinking, Chris, I would just type history in to my terminal and look at what I did to make it work. Well, that’s a solution for sure, but not for many of us, especially if your wife had accidentally tipped a glass of wine over your old Macbook during the interim.

To make things trickier, as mentioned, some things that blow up on one platform, work splendidly on another. A case in point would be when you have failing dependencies that are Android specific, which allow you to run your iOS project but blow up on building your Android project. Hair loss time.

Firstly, check your Ionic info:

ionic info

Your system information:

  • Cordova CLI: 5.0.0
  • Gulp version: CLI version 3.8.1
  • Gulp local: Local version 3.8.11
  • Ionic Version: 1.0.0
  • Ionic CLI Version: 1.4.6-beta.0
  • Ionic App Lib Version: 0.0.22
  • ios-deploy version: Not installed
  • ios-sim version: 3.1.1
  • OS: Mac OS X Yosemite
  • Node Version: v0.12.4
  • Xcode version: Xcode 6.3.1 Build version 6D1002

Updating Node is pretty straightforward on Mac – just download it and run the installer.

As part of getting this to work today, I also ran:

sudo npm install -g driftyco/ionic-cli
sudo npm install -g cordova
sudo npm install -g ionic

I think that can be done as a one-liner, but I didn’t, so I am showing each one as it is in my history.

Android Specific Stuff

Quite possibly the second most time intensive part of this whole process – after debugging why Local Notifications kept blowing up – was the download and install the latest Android SDK stuff. Man. Alive. It took ages.

On Mac:

brew install android-sdk

That bit was pretty quick. Then, as prompted, run:

export ANDROID_HOME=/usr/local/opt/android-sdk

Check the path given by brew installer, as the above may not work for you.

Then:

android

This pops up the Android SDK Manager, which *should* go off and search for all the latest Android stuff that you will need to download (which takes bloody ages), and install (which takes about the same amount of time as downloading).

Android SDK on Mac OSX

I hit a bit of trouble here which will likely not affect you. As I record my training videos on a distraction free second profile, and because I am making this app as part of a course for Code Review, I couldn’t run the SDK manager from the profile I hadn’t run the brew install android-sdk command from originally. This ultimately involved switching profiles whenever I have needed to update the SDK – which is more frequently than I had anticipated (every other day so far, but then, Android M is releasing currently).

Keeping Ionic in Sync with your Installed Plugins

As mentioned, to get in app Local Notifications to work involved using a plugin.

Installing this plugin is described on the plugin authors website as:

cordova plugin add https://github.com/katzer/cordova-plugin-local-notifications

That was my initial move. There are some other suggested ways of installing this plugin on that website, and I tried all of them.

The problem was, I kept hitting on issues when trying to build for Android, which were predominantly dependency names used in the Katzer provided config.xml which were 404’ing when npm was trying to download them.

Here’s a good example of what I mean:

tweetHours git:(master) ✗ ionic platform rm android && ionic platform add android 
Updated the hooks directory to have execute permissions 
Removing platform from package.json file 
Updated the hooks directory to have execute permissions Adding android project... 
Creating Cordova project for the Android platform: 
  Path: platforms/android 
  Package: com.ionicframework.tweethours247847 
  Name: tweetHours 
  Activity: MainActivity 
  Android target: android-22 
Copying template files... 
Android project created with cordova-android@4.0.0 
Running command: /Users/Shared/Development/tweetHours/hooks/after_prepare/010_add_platform_class.js /Users/Shared/Development/tweetHours 
add to body class: platform-android 
Installing "com.ionic.keyboard" for android 
Installing "cordova-plugin-android-support-v4" for android 
Installing "cordova-plugin-device" for android 
Installing "cordova-plugin-whitelist" for android 
Installing "de.appplant.cordova.common.registerusernotificationsettings" for android 
Installing "de.appplant.cordova.plugin.local-notification" for android 
WARNING: android.support.v4 has been renamed to cordova-plugin-android-support-v4. You may not be getting the latest version! We suggest you cordova plugin rm android.support.v4 and cordova plugin add cordova-plugin-android-support-v4.
Fetching plugin "android.support.v4" via cordova plugins registry 
Fetching from cordova plugins registry failed: EACCES, open '/Users/codereview/.plugman/cache/b41e20d1-android-support-v4.lock' 
Fetching plugin "android.support.v4" via npm 
npm http GET https://registry.npmjs.org/android.support.v4 
npm http 404 https://registry.npmjs.org/android.support.v4 
Fetching from npm failed: 404 Not Found: android.support.v4 Failed to install 'de.appplant.cordova.plugin.local-notification':Error: 404 Not Found: android.support.v4 at RegClient. (/usr/local/lib/node_modules/cordova/node_modules/cordova-lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:268:14) at Request.self.callback (/usr/local/lib/node_modules/cordova/node_modules/cordova-lib/node_modules/npm/node_modules/request/index.js:148:22) at Request.emit (events.js:110:17) at Request. (/usr/local/lib/node_modules/cordova/node_modules/cordova-lib/node_modules/npm/node_modules/request/index.js:876:14) at Request.emit (events.js:129:20) at IncomingMessage. (/usr/local/lib/node_modules/cordova/node_modules/cordova-lib/node_modules/npm/node_modules/request/index.js:827:12) at IncomingMessage.emit (events.js:129:20) at stream_readable.js:908:16 at process.tickCallback (node.js:355:11) Error: 404 Not Found: android.support.v4 at RegClient. (/usr/local/lib/node_modules/cordova/node_modules/cordova-lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:268:14) at Request.self.callback (/usr/local/lib/nodemodules/cordova/node_modules/cordova-lib/node_modules/npm/node_modules/request/index.js:148:22) at Request.emit (events.js:110:17) at Request. (/usr/local/lib/nodemodules/cordova/node_modules/cordova-lib/node_modules/npm/node_modules/request/index.js:876:14) at Request.emit (events.js:129:20) at IncomingMessage. (/usr/local/lib/node_modules/cordova/node_modules/cordova-lib/node_modules/npm/node_modules/request/index.js:827:12) at IncomingMessage.emit (events.js:129:20) at stream_readable.js:908:16 at process.tickCallback (node.js:355:11)

It would then promptly bomb out and blow up.

The important line is:

WARNING: android.support.v4 has been renamed to cordova-plugin-android-support-v4. You may not be getting the latest version! We suggest you cordova plugin rm android.support.v4 and cordova plugin add cordova-plugin-android-support-v4.

I tried the obvious – running:

cordova plugin rm android.support.v4 and cordova plugin add cordova-plugin-android-support-v4

And that kinda works. It works because it does what it says on the tin. But the issue is that it’s not you who is managing that dependency, it’s the plugin you are trying to use – in this case, it’s a dependency of the Local Notification plugin (I think, I hit so many of these that this was just the first one I found in my terminal when scrolling back).

As this is a dependency of a plugin, you can’t – directly – change this without submitting a Pull Request, waiting for that PR to be approved, merged, and then having the new version imported. I couldn’t wait that long, and I’m always reluctant to submit a PR to a project I don’t understand how to test. So, instead, I forked it.

I updated the plugins to use the new paths for the dependencies, and all was sorted – on that front. If you’re interested, click here to see the commit history.

For reference, my package.json file looks like this:

{
  "name": "tweethours",
  "version": "0.0.2",
  "description": "tweetHours: An Ionic project",
  "dependencies": {
    "gulp": "^3.5.6",
    "gulp-concat": "^2.2.0",
    "gulp-minify-css": "^0.3.0",
    "gulp-rename": "^1.2.0",
    "gulp-sass": "^1.3.3"
  },
  "devDependencies": {
    "angular-mocks": "^1.4.0",
    "bower": "^1.3.3",
    "gulp-util": "^2.2.14",
    "shelljs": "^0.3.0",
    "karma-chrome-launcher": "^0.1.4",
    "karma-jasmine": "^0.1.5"
  },
  "cordovaPlugins": [
    "cordova-plugin-android-support-v4",
    "cordova-plugin-device",
    "cordova-plugin-whitelist",
    "com.ionic.keyboard",
    {
      "locator": "https://github.com/a6software/cordova-plugin-local-notifications.git",
      "id": "de.appplant.cordova.plugin.local-notification"
    },
    {
      "locator": "https://github.com/katzer/cordova-common-registerusernotificationsettings",
      "id": "de.appplant.cordova.common.registerusernotificationsettings"
    },
    {
      "locator": "https://github.com/EddyVerbruggen/Toast-PhoneGap-Plugin",
      "id": "nl.x-services.plugins.toast"
    }
  ],
  "cordovaPlatforms": [
    "ios",
    {
      "platform": "ios",
      "version": "",
      "locator": "ios"
    },
    "android"
  ]
}

You can make ionic do the dirty work for you on keeping this file updated, just run: ionic state save – but before you do, make sure you have a back up of your package.json just in case.

The reason some of the plugins use the locator and id properties inside their own JSON object is that I added Github repo URLs instead of the package names, e.g.:

cordova plugin add https://github.com/a6software/cordova-plugin-local-notifications.git

instead of:

cordova plugin add de.appplant.cordova.plugin.local-notification

More Problems On Android

However, when building I was still hitting on another issue – this time one with an outstanding pull request.

:generateDebugSources :compileDebugJava/Users/Shared/Development/tweetHours/platforms/android/src/de/appplant/cordova/plugin/localnotification/LocalNotification.java:564: error: cannot find symbol webView.evaluateJavascript(js, null); symbol: method evaluateJavascript(String,) location: variable webView of type CordovaWebView /Users/Shared/Development/tweetHours/platforms/android/src/de/appplant/cordova/plugin/localnotification/LocalNotification.java:561: error: cannot find symbol webView.post(new Runnable(){ symbol: method post() location: variable webView of type CordovaWebView Note: /Users/Shared/Development/tweetHours/platforms/android/src/com/ionic/keyboard/IonicKeyboard.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details. 2 errors FAILED

FAILURE: Build failed with an exception.

For me, this issue occured when running ionic run android – I don’t use the emulator, I can’t seem to make the thing work for Android, so this was deploying to a real Android phone.

I applied the fix directly to my own fork, and that resolved the issue.

Don’t Be Afraid To Remove Your Platform

Seemingly the best way to ensure your project is fresh is to remove and re-add your platform whenever you make a change:

ionic platform r, android && ionic platform add android

This way, you can be sure you are getting the exact plugins you expect rather than some existing ones that may be cached / different to what you expect.

This process *should* blow up if any of your plugins have errors / unmet dependencies etc.

Other Things I Had To Do

These bits are things I did which I am unsure whether they helped, but they were done so are here for reference:

sudo chown -R {yourUsername} ~/.android

I found my ~/.android (my user’s home directory /.android dir) was owned by root. This caused the build to fail. The message was quite clear on this. Not sure how it ended up owned by root in the first place mind.

ionic browser remove crosswalk

(http://ionicframework.com/docs/cli/browsers.html)

In App Notifications Working!

android local notifications with ionic
android local notifications with ionic

Success. At last! 🙂

Published by

Code Review

CodeReviewVideos is a video training site helping software developers learn Symfony faster and easier.

4 thoughts on “In App Notifications with the Ionic Framework”

  1. could you please help with an example for clicking on notification with app’s state ‘background’? im testing with a simple alert on clicking on notification, it works only when app is in foreground, i need a modal window to present user a form on notification click.

    1. Hi Faizan,

      I don’t have any experience of doing this either – Google / StackOverflow / Ionic forums would be best for this one.

      Sorry I couldn’t help further at this time,

      Chris

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.