Fixing Ionic Icon and Splashscreen Being Stuck as Default

I want to share my fix for the Ionic icon and splashscreen issue I just hit upon.

The issue was that no matter what I did, deploying my Ionic app to my Android phone always ended up with an empty / blank / white splash screen and the default ionic icon.

Ionic default icon on Android
Ionic default icon on Android

No matter what I seemed to do, that darned icon would not change.

Before I go further however, it is definitely worth pointing out an awesome feature in Ionic – ionic resources – a command line utility which will take an icon and a splash screen image (.png files, Photoshop .psd files, or Illustrator .ai files) and create you all the correctly sized icons / splash screen images AND update your config.xml file for you. Of course, you need to have the two images to begin with – and if you don’t, check out somewhere like fiverr or odesk (ahem, upwork?) to have these files made for you.

From my project root, I put my icon.png and splash.png into a folder called resources and then ran the ionic resources command. Checking my config.xml after this shows something along the lines of:


  *snip* 
  <platform name="android">
    <icon density="ldpi" src="resources/android/icon/drawable-ldpi-icon.png"/>
    <icon density="mdpi" src="resources/android/icon/drawable-mdpi-icon.png"/>
    <icon density="hdpi" src="resources/android/icon/drawable-hdpi-icon.png"/>
    <icon density="xhdpi" src="resources/android/icon/drawable-xhdpi-icon.png"/>
    <icon density="xxhdpi" src="resources/android/icon/drawable-xxhdpi-icon.png"/>
    <icon density="xxxhdpi" src="resources/android/icon/drawable-xxxhdpi-icon.png"/>
    <splash density="land-ldpi" src="resources/android/splash/drawable-land-ldpi-screen.png"/>
    <splash density="land-mdpi" src="resources/android/splash/drawable-land-mdpi-screen.png"/>
    <splash density="land-hdpi" src="resources/android/splash/drawable-land-hdpi-screen.png"/>
    <splash density="land-xhdpi" src="resources/android/splash/drawable-land-xhdpi-screen.png"/>
    <splash density="land-xxhdpi" src="resources/android/splash/drawable-land-xxhdpi-screen.png"/>
    <splash density="land-xxxhdpi" src="resources/android/splash/drawable-land-xxxhdpi-screen.png"/>
    <splash density="port-ldpi" src="resources/android/splash/drawable-port-ldpi-screen.png"/>
    <splash density="port-mdpi" src="resources/android/splash/drawable-port-mdpi-screen.png"/>
    <splash density="port-hdpi" src="resources/android/splash/drawable-port-hdpi-screen.png"/>
    <splash density="port-xhdpi" src="resources/android/splash/drawable-port-xhdpi-screen.png"/>
    <splash density="port-xxhdpi" src="resources/android/splash/drawable-port-xxhdpi-screen.png"/>
    <splash density="port-xxxhdpi" src="resources/android/splash/drawable-port-xxxhdpi-screen.png"/>
  </platform>
*snip*

And each of those resources/android… whatever paths is an actual file, resized properly.

Ok, enough of the set up. As mentioned, all this looked right yet still every time I did an ionic run android, the icon would be the default icon, and the splash screen was empty / blank / white.

There are a number of apparent fixes to this problem online, but none seemed to work for me. The best I could tell, my ionic icon was cached somehow, but as I was building on the Mac, I didn’t have any form of Android tooling available to open the project and clean the install.

Ionic Icon Woes : The Solution

Though it took a lot of trial and error (precious hours I will never get back!), the solution to this problem was pretty easy.

I had noticed the widget id value in my config.xml was a bit odd. For some reason (which I haven’t figured out still), when Ionic had initially generated my project skeleton, it had added some numbers on at the end.


<widget id="com.ionicframework.tweethours24237" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
*snip*

All it took to fix my Ionic icon problem was to remove these numbers.

As soon as I did that, my icon changed to what I expected:

Ionic icon on Android displaying as expected
Ionic icon on Android displaying as expected

Result!

Splash Screen Solution

Unfortunately, this didn’t fix my splash screen issue.

But, as above, I had correctly generated the splash screen so this was a little puzzling. Everything seemed ok – and I did see the 3 seconds of white / blank / emptiness that implied the splash screen code was actually executing, just missing the expected image.

To fix this I added the cordova-plugin-splashscreen as mentioned here.

Remember to update your package.json file whenever you add a plugin, so you or your colleagues aren’t caught out by a missing plugin at some point in the future.

# /packages.json
  *snip*
  "cordovaPlugins": [
    "cordova-plugin-splashscreen",
  *snip*

To make this work I also needed to update my .run method:

# /www/js/app.js
.run(function($rootScope, $ionicPlatform, $cordovaSplashscreen){
    setTimeout(function() {
        $cordovaSplashscreen.hide()
    }, 3000);

After that, my splash screen worked as expected:

Custom Ionic Splash Screen on Android
Custom Ionic Splash Screen on Android

And finally, I was done 🙂

For reference, here is my ionic info on the off chance this is useful:

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

Also, my base image sizes are as follows:

icon.png – 768px x 768px
splash.png – 2208px x 2208px

I also used this splash template – just make sure you get everything between the green grid lines, or you will get unwanted clipping on smaller screen sizes.

Angular Services with Karma and Jasmine Testing

viva las vegas - karma and jasmine go testing

Karma and Jasmine. Not the names of two girls I partied with in Vegas. Something even better.

Yes, let me introduce you to Jasmine testing, a Behaviour Driven Development testing framework. And her friend Karma, a spectacular test runner.

Now we are done with the introductions, let’s get down to detail. Testing an AngularJS / Ionic application is well documented and baked right in to the framework. That’s cool. Sometimes though, if your project reaches any sort of complexity above the tutorials, you are going to need to test Angular services that themselves have dependencies.

I found the documentation on this topic sparse and confusing. So hopefully this example can help shed a little more light on the subject.

The Situation

How can we unit test an Angular service that has another service (dependency) injected in to it?

Let’s say we have two services that we care about. Service A, and Service B.

Service A receives Service B via dependency injection.

How do we mock this up so that it becomes testable?

How can we fake the data coming back from Service B in a way that will allow us to make the dependency behave exactly how we want it to?

And how can we do this in such a way that our individual tests don’t become huge and unwieldy?

Before Each, Throw Me a Curve Ball

Assuming we have a service definition that looks like this:

“` language-javascript
‘use strict’;

angular.module(‘core’)

.service(‘TweetHoursStorage’, [‘storage’, function($localStorage) {
this.clearAll = function() {
$localStorage.clearAll();
};

// * snip *

}])
;
“`

Let’s say we have a test file that looks something like this:

(note: don’t copy / paste this, as it doesn’t work)

// app/modules/core/tests/services/tweet-hours-storage.service.test.js
'use strict';

describe('Service: TweetHoursStorage', function () {

    // load the service's module
    beforeEach(module('core'));

    var TweetHoursStorage;

    beforeEach(inject(function(_TweetHoursStorage_) {
        TweetHoursStorage = _TweetHoursStorage_;
    }));

    afterEach(function () {
        TweetHoursStorage.clearAll();
    });

    describe('update method test', function() {

        it('Tests the update method returns false if not passed an object', inject(function() {
            expect(TweetHoursStorage.update('blah')).toBeFalsy();
        }));

    // * snip *

When we run this, we will see an error similar to the following:

TypeError: queueableFn.fn.call is not a function

Why?

Well, it turns out there are two problems with the above code. One is more visible than the other, but we can fix both at the same time.

This issue occurs before we even hit our first test proper. This is a set up issue.

Firstly, beforeEach(module('core')); – this is redundant. We can remove this and combine with the second beforeEach(); method call just below.

Let’s do that:

// app/modules/core/tests/services/tweet-hours-storage.service.test.js
'use strict';

describe('Service: TweetHoursStorage', function () {

    var TweetHoursStorage,
        mockLocalStorage;

    // load the service's module
    beforeEach(function() {
        module('core', function($provide) {
            mockLocalStorage = jasmine.createSpyObj('mockLocalStorage', ['clearAll']);
            $provide.value('storage', mockLocalStorage);
        });

        inject(function(_TweetHoursStorage_) {
            TweetHoursStorage = _TweetHoursStorage_;
        });
    });

    afterEach(function () {
        TweetHoursStorage.clearAll();
    });

    describe('a clear all method test', function() {
        // * snip *
    });

Now, lines 11-14 go about creating a Jasmine spy object which allows us to pretend mockLocalStorage is really a real life, working as expected instance of $localStorage, which the service we are testing actually depends on – the line from our service definition above for reference:

.service('TweetHoursStorage', ['storage', function($localStorage) {

Once we implement this our tests should actually start running. They may / likely won’t be passing at this stage, but that bit is easier (in my opinion at least) than getting this set up the first time you do it.

Faking an API Call when Jasmine Testing

What about if we have a remote API that we need to get some data from?

How can we mock that out in a test?

 // app/modules/core/tests/services/selected-hours-to-notification-data-transformer.test.js

'use strict';

describe('Service: SelectedHoursToNotificationDataTransformer', function () {

    var SelectedHoursToNotificationDataTransformer,
        mockApiLookup,
        mockDateProcessing;


    // load the service's module
    beforeEach(function (){
        module('core', function($provide) {
            mockApiLookup = jasmine.createSpyObj('mockApiLookup', ['getAll']);
            mockApiLookup.getAll.and.callFake(function(){ return getFakeDataSet(); });
            mockDateProcessing = jasmine.createSpyObj('mockDateProcessing', ['process', 'nextStartDate']);

            $provide.value('ApiLookup', mockApiLookup);
            $provide.value('DateProcessing', mockDateProcessing);
        });

        inject(function(_SelectedHoursToNotificationDataTransformer_) {
            SelectedHoursToNotificationDataTransformer = _SelectedHoursToNotificationDataTransformer_;
        });
    });


    /**
     * This is the pretend result set that might be returned by a call to the API GET end point
     * @returns {*[]}
     */
    var start = moment().subtract(6, 'days');
    var getFakeDataSet = function () {
        return [
            { id: 4, title: 'event4', start: start, duration: 2 },
            { id: 5, title: 'event5', start: start, duration: 1 },
            { id: 8, title: 'event8', start: start, duration: 1 },
            { id: 99, title: 'event99', start: start, duration: 1 }
        ];
    };

This code may not be the most elegant or ninja-level JavaScript ever written, but by jingo it shows a few things that I would have found helpful a few months hence.

Line 15 – we are doing much the same as in the previous example.

This time though, we want to replace the real outcome with one we can control. Testing a remote API during our testing is not a good thing for many reasons including the API likely not being entirely tolerant to us repeatedly sending in the same fake data every time we run our Jasmine test suite. Also, speed, predictability, and many other factors.

First we declare the method as being available on our mock – line 15.

Then we tell Jasmine what we want anything that calls that method to get instead:

mockApiLookup.getAll.and.callFake(function(){ return getFakeDataSet(); });

Further down, starting on line 34, we do something that feels a little different to you if you’re used to working with PHP. We declare var getFakeDataSet which contains a yet to be executed function.

When that function is executed, it will return an array of JSON, representing the data we would anticipate receiving back from our remote API in the real world.

That data set will be entirely dependent on your project’s circumstances. My data is just for show.

So, back to line 16, where we return that data. The reason for me going into detail there is that it is entirely possible – and correct in some circumstances – that you would want to return the unresolved function.

mockApiLookup.getAll.and.callFake(function(){ return getFakeDataSet; });
// vs
mockApiLookup.getAll.and.callFake(function(){ return getFakeDataSet(); });

Just be careful of this one. It’s not specific to Jasmine testing, it’s a JavaScript thing in general, but it can be confusing.

I digress, but essentially the first line will return the function, unresolved until you call it by adding the () to the end of the variable holding that function. Confusing? A little. Tricky to spot for sure, when you inevitably make that mistake whilst coding.

Back to the example – line 19 and 20 show how to add multiple mocked dependencies should your service require it.

Jasmine Testing Cheat Sheet

The manual is pretty good on this topic.

But I also found this cheat sheet very helpful.

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! 🙂

2 Things I Wish I Knew Earlier About Ionic Nav Menu

There’s a couple of issues I have hit on today as I was working through an Ionic nav menu that I wish I knew about earlier.

The good news is, both can be demonstrated in the same code sample.

Let’s see the code first, then go through what it’s doing:

<ion-side-menu side="left" expose-aside-when="large">
    <header class="bar bar-header bar-calm">
        <h1 class="title">Menu</h1>
    </header>

    <ion-content class="has-header">
        <ion-list>
            <ion-item ng-repeat="dayName in days"
                      menu-close
                      ui-sref="app.day-view({day:'{{ dayName }}'})"
                      nav-clear
                      ng-class="isToday(dayName) ? 'item-stable' : ''">
                {{ dayName }}
            </ion-item>
        </ion-list>
    </ion-content>
</ion-side-menu>

Upper Class

The first is that when using the ion-item it’s possible to use the Ionic predefined CSS styles to give your navigation / list items a bit of colour.

The available options are the same that are available for everything else, just prefixed with item:

  • item-light
  • item-stable
  • item-positive
  • item-assertive
  • item-balanced
  • item-energized
  • item-royal
  • item-dark

If you’re unsure about what any of them look like, this is a good example.

Notice, this goes on the ion-item definition, and although I have used ng-class in the example, this can be swapped out for the standard css style class.

Standard CSS example:

<ion-item class="item-stable">list item text here</ion-item>

More dynamic method using ng-class and the ternary operator to do an inline or shorthand if / else.

<ion-item ng-class="yourFunction(yourProperty) ? 'item-stable' : 'item-light'">
  {{ yourProperty }}
</ion-item>

Ionic Losing My History

I’m going to cut right to the chase on this one – if you are finding your ‘back’ operations get all screwed up when using a side menu, then search your project for instances of nav-clear and remove with extreme prejudice.

No doubt this is wanted in some circumstances, but it cost me at least an hour of head scratching today – not helped by the Ionic forums being offline for some reason.

It turns out this nav-clear attribute deletes history, in my case, whenever a user clicks on a different menu option. Not at all what I wanted.

The documentation for this is good, but it’s seemingly hidden. I could only find it with a well thought out (read: after a million attempts) Google, but frustratingly it is all there if you know what to search for.

It just doesn’t show up on the menu anywhere as far as I can see.

Always Open Sidebar (On iPad or Similar)

Even though I said I would do two tips, this third one is worth mentioning. This one didn’t actually cost me any time – it was found whilst Googling for the other problems above.

Anyway, it’s too good not to share.

If you have a side menu and you think it would be a good thing if it was always open, should the device have enough screen space (e.g. on an iPad or similar), then stick in the following:

expose-aside-when="large"

And so long as your device width is >768px it will always show the sidebar.

This one is right there in the documentation, but I thought it was awesome and worth sharing.