Category: Cordova

Link back to Android app using intent-filter

This post covers a simple scenario where we have a web page (for example, on our organization’s mobile site) that we would like to link back to the mobile app.

In our AndroidManifest.xml, create an intent-filter element as follows:

<intent-filter>
    <data android:scheme="ourorg" />
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.BROWSABLE" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

If you place this under the main element representing your app, then the following link will invoke your app.
Now the link ourorg:// will link back to the app.

There’s a lot more that you can do with the intent-filter. For now, we’re just covering a simple case here.

More information on intent-filter on developer.android.com

Signing Your Cordova Android App

So your app is tested, and relatively bug-free – congratulations! Now it’s time to publish on Google Play. You’ll find out pretty quickly that there are a couple of modifications you’ll need to make to your app.

Unfortunately I found that I had to do quite a bit of digging to get my release process *almost* automated. There are full instructions for this here in the Cordova docs, but I’m offering a simpler explanation that will work signing into the automation.

Version Code

You’ll need to change the android:versionCode property in your AndroidManifest.xml file. Google Play will treat your app as a new version every time this value is incremented. If anyone knows how to work this into the Cordova automation, please comment on this post – I would love to hear about it. My current thought is that Cordova Hooks should work for this, but at the moment I’m not going down this path.

<manifest android:hardwareAccelerated="true" android:versionCode="2" android:versionName="1.2" ....

Creating the Key for Signing

First, create a keystore, using the keygen tool. In the following example, I’m generating one for my fictitious client, NCA:

$ keytool -genkey -v -keystore nca.keystore -alias ncamobile -keyalg RSA -keysize 2048 -validity 10000
Enter keystore password:
Re-enter new password:
What is your first and last name?
  [Unknown]:  Michael Mendelson
What is the name of your organizational unit?
  [Unknown]:  NCA
What is the name of your organization?
  [Unknown]:  National Client of America
What is the name of your City or Locality?
  [Unknown]:  San Francisco
What is the name of your State or Province?
  [Unknown]:  CA
What is the two-letter country code for this unit?
  [Unknown]:  US
Is CN=Michael Mendelson, OU=NCA, O=National Client of America, L=San Francisco, ST=CA, C=US correct?
  [no]:  yes

Generating 2,048 bit RSA key pair and self-signed certificate (SHA256withRSA) with a validity of 10,000 days
        for: CN=Michael Mendelson, OU=NCA, O=National Client of America, L=San Francisco, ST=CA, C=US
Enter key password for <ncamobile>
        (RETURN if same as keystore password):
[Storing nca.keystore]

Show the Android Build Scripts Where the Keystore is

When you build in release mode using Cordova, the Android build scripts are invoked. You’ll need to add some properties to a place where the build can see them. If you’d rather not enter the passwords, you’ll be prompted during the build.

Cordova 4.0+

Cordova currently builds with Gradle. When the –release switch is used, Gradle scripts will look for a file called release-signing.properties in platforms/android. Create it, and add the following settings:

storeFile=../../nca.keystore
storeType=jks
keyAlias=ncamobile
keyPassword=passwordhere
storePassword=passwordhere

There are a few other ways to do this. There’s a great StackOverflow reference for that. There’s also more detail here in the Cordova docs

Older Cordova

Properties need to be in a place where Ant can find them. I added them to my platforms/android/local.properties file.

key.store=c:\\dev\\ncamobile\\nca.keystore
key.alias=ncamobile
key.store.password=passwordhere
key.alias.password=passwordhere

Run the Release Build

The following should do it:

cordova build android --release

If you build without the –release argument, the app will still be signed with the Debug Key.

One issue I’d like to overcome is that items in AndroidManifest.xml and local.properties are NOT checked into our code repository, and in fact are regenerated, modified or overwritten by various Cordova processes. While it would make sense that configuration properties like this one would be set-able into the config.xml, that does not appear to be the case. Of course that impedes our ability to just check out the code and run a release build.

I’ll look for ways to smooth out this process in the future, and any insights are welcome.

Use Cordova to Initiate an Email

Since your Cordova app runs in an HTML Layout Engine, there’s a very straight-forward way to initiate an email: a link.

However, if you want to create a nicely formatted email, or a longer one, you need to use a plugin to invoke the native interface. I went with katzer’s cordova-plugin-email-composer, and it works well.

My purpose was to simply create a blank email draft, with only title and content.

The following code wrapper encapsulates the plugin call, taking into account:

  • we’re running on a device with email not installed
  • we’re running on a non-mobile device (as I often do when debugging the app)
function draftEmail(subject, message) {
    if (!window.plugin){
        //non-mobile - plugins are not present.
        alert("Email plugin is not available");  
        return;
    }
    if (!isAvailable){
        //mobile, but no email installed
        alert("Email is not available")
        return;
    }
    window.plugin.email.open({
        to: [], cc: [], bcc: [], attachments: [],
        subject: subject,
        body: message,
        isHtml: true
    });
}

//runs on application startup, to check for installed email
var isAvailable = false;
if (window.plugin){
    window.plugin.email.isServiceAvailable(
        function (emailInstalled) {
            isAvailable = emailInstalled;
        }
    );
}

It would be more elegant to hide the “Share” button if email is not installed on the device, but for demo purposes this works.

Build Configurations in Cordova using Hooks

When working with Cordova or Phone Gap, you might need a way to configure your dev build differently from your production build. For example, you might have a set of web services to be used for development, and another for production. This simple Cordova Hook loads the configuration appropriate to the build.

Hooks are a well documented mechanism in Cordova which executes the developer’s custom scripts at certain points in the build process. The best example I have found is this post by Dan Moore.

In this example, all settings for a given configuration are in one file. The appropriate files is substituted in when you build (or “prepare”) your project. This is written in node.js for portability.

  1. Wire up your code so that all “switchable” settings are in one file, config.js. Here’s an example:
    var config = {
        oneSvc: "http://www.example.com/ws/MyService.svc/MyService",
        anotherSvc: "http://www.example.com/ws/YourService.svc/YourService"
    }
    

    …then use config.oneSvc and config.anotherSvc to access the settings in your code. Remember to include config.js in your index.html.

  2. Create an alternate config file in your config directory, called config-prod.js or config-test.js.
  3. In your development environment, set a variable called TARGET with the appropriate value (e.g. “prod” or “test”). If this variable is not set, config.js will be used as-is.
  4. Place the following code in your .cordova/hooks/after_prepare directory.
  5. Build the project as usual.
#!/usr/bin/env node
/**
 * Created by Michael on 5/13/2014.
 *
 * Detect the current build by looking at the TARGET env variable.
 * If TARGET does not exist, make no change.
 * Otherwise, replace config.js with config/config-xxxx.js (where TARGET=xxxx)
 * If config-xxxx.js doesn't exist, execution will fail.
 *
 * Before running this, set the target as:
 *    export TARGET=prod
 *
 */
var fs = require("fs");
var path = require("path");
var rootdir = process.argv[2];
console.log("Running hook: "+path.basename(process.env.CORDOVA_HOOK));

if (process.env.TARGET) {
    var srcfile = path.join(rootdir, "config", "config-"+process.env.TARGET+".js");

    //do this for each platform
    var configFilesToReplace = {
        "android" : "platforms/android/assets/www/js/config.js"
        ," ios" : "platforms/ios/www/js/config.js"
    };

    for(var platform in configFilesToReplace) {
        console.log("Modifying config for platform "+platform+", TARGET="+process.env.TARGET);
        var destfile = path.join(rootdir, configFilesToReplace[platform]);

        if (!fs.existsSync(srcfile)) {
            throw "Missing config file: "+srcfile;
        } else {
            console.log("copying "+srcfile+" to "+destfile);
            fs.createReadStream(srcfile).pipe(fs.createWriteStream(destfile));
        }
    }
} else {
    console.log("TARGET environment variable is not set.  Using default values.");
}

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:

GCM Explained

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', &quot;Hello Cordova!&quot;);
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, {&quot;senderID&quot;:gcmAppID,&quot;ecb&quot;:&quot;onNotificationGCM&quot;});
        }
        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 -&gt; RECEIVED:' + e.event + '');
    switch( e.event )
    {
        case 'registered':
            if ( e.regid.length &gt; 0 )
            {
                console.log('REGISTERED with GCM Server -&gt; REGID:' + e.regid + &quot;&quot;);

                //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(&quot;/android_asset/www/&quot;+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 = &quot;#/tab/featured&quot;;
            }

            console.log('MESSAGE -&gt; MSG: ' + e.payload.message + '');
            console.log('MESSAGE: '+ JSON.stringify(e.payload));
            break;

        case 'error':
            console.log('ERROR -&gt; MSG:' + e.msg + '');
            break;

        default:
            console.log('EVENT -&gt; 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:

Cordova and AngularJS – Opening links in system browser

In the course of writing my Android/Cordova/Ionic app, I ran into another tricky requirement: Display HTML content from a web service, containing links. These links should open in the user’s preferred browser. In the end, there are several steps necessary to getting this right.

Step 1: Display unescaped HTML in the app.

When AngularJS binds content, special characters are “escaped”, so HTML tags are visible and of course non-functional. Fortunately AngularJS has a fix for this: ng-bind-html. Use this instead of ng-bind (which is equivalent to the double-bracket method). Your content will be slightly sanitized (which will be a problem later), but the links are preserved.

<div ng-bind-html="content.Description"></div>

At this point you’ll see some strange behavior: the links open inside webkit. So the user stays in your app, but there is no way to go back to your Angular HTML5 pages. In my case, even the back button did not work!

As with so many Cordova issues, “there’s a plugin for that.”

Step 2: Install the inappbrowser plugin

Despite its name, the inappbrowser plugin enables the behavior we’re looking for – opening a browser window EXTERNAL to our application. The following command will install it:

cordova plugin add org.apache.cordova.inappbrowser

In theory, all you have to do is set up your links as follows:

window.open('http://intown.biz', '_system', 'location=yes');
Take Note: There might be a better solution for step 3, that does not require Jquery. See comment by Rewon below.

 

Step 3: Apply the “inappbrowser” approach (part 1).

The next question is how to apply this to your downloaded content? Because of AngularJS, there are two issues:

  • JavaScript is “sanitized” (i.e. not rendered) by ng-bind-html. Otherwise, we would be able to write a filter to replace our ordinary Anchor tags with tags that invoke the javascript you see above.
  • I tried directives, but since I don’t have control over the HTML being passed in, that proved more complicated.  Depending on your circumstances, a directive may work for you.

I finally arrived at a two step process. First, use a simple custom filter to add ‘class=”ex-link”‘ to each section where we want the links to open in the external browser.

.filter('externalLinks', function() {
   return function(text) {
     return String(text).replace(/href=/gm, "class=\"ex-link\" href=");
   }
 })

The above filter would need to be more sophisticated if there are classes within the HTML that need to be preserved. Applying the filter in AngularJS, your HTML now looks like this:

<div ng-bind-html="content.Description | externalLinks"></div>

Step 3: Apply the “inappbrowser” approach (part 2).

Finally, we address the question of how to force our links to open in the _system window using JavaScript. For this we’ll resort to JQuery. I generally like to keep JQuery out of my projects, but in my case it was already included for a third-party tool. If anyone knows of a “pure Angular” approach to this, I’m all ears.

Using $timeout ensures that this handler is one of the last applied to the page.

$timeout(function () {
   $('.ex-link').click(function () {
     var url = $(this).attr('href');
     window.open(encodeURI(url), '_system', 'location=yes');
     return false;
   })
})

You might place the above code into the controller of the page that needs it, or in a factory containing utility functions. Don’t forget to inject $timeout in the appropriate place.

This two-step approach has advantages: obviously the “ex-link” class can be added directly to a template’s HTML, allowing you to place an “external link” anywhere in your app, and distinguish from your app’s internal links.

Of course there are some risks to displaying foreign HTML in a Cordova app. For my project, I have been assured that the HTML will be correct, but of course if it’s not, my app could break. So down the road I may add HTML integrity checking, but in the Agile tradition, I will only address this if it’s a real issue.

Changing the Cordova app icon

It seems that many Cordova tasks, even fundamental ones, require some degree of research.  In this case, I first found a solution that was “close enough”, and then found a better one.  Both are shown here.

The goal here is to use your own custom icon instead of the default icon.

The Sub-optimal Way

1. Find the drawable directory, and place the icon in it (Yes, I see there are actually 5 drawable directories.  For now I only care about one.)

drawable directory

2. Find the AndroidManifest file, and modify it to point to your file.  Note: leave out the extension of the icon file.

android manifest

<application android:hardwareAccelerated=”true” android:icon=”@drawable/myIcon” android:label=”@string/app_name”>

3. Compile and run.

The Better Way

This way is better because as a best practice, it’s good to keep everything in the platforms/android directory generic.  Ideally you should be able to check out the code in a clean environment, use the cordova add platform android command and be ready to go.

This time we don’t have to change the manifest, because we’re just replacing the icon.png file by copying over it.

We achieve this using hooks, which are custom commands that run during the build process.  This particular command copies the icon files into their appropriate directories. Here’s the process:

  1. Create a folder for hooks as shown in the image.  We’ll focus on the “after_prepare” point of the build, but there are a number of other points where you can run commands, as detailed here.
  2. Create a configuration directory in the root, as shown. You could really put these files anywhere, if you want to use your own organizational structure.
    cordova resource files
  3. Download these hooks:  http://www.mooreds.com/sample-hooks.tar.gz.  You’ll only need the one called 030_resource_files.js, though the others could come in handy too.  They are explained by Dan Moore on Devgirl’s excellent blog.
  4. Tweak the file to suit your own nefarious needs.

Next time you build, your files should be copied into the appropriate places automatically.  And you still don’t need to check in your platforms directory!

Renaming a Cordova application

As my loyal readers know, I’ve been working on my first Cordova application, focusing on Android.  Of course I found the Android emulator to be very slow, so I worked solely in a browser for a few days.

When I tried installing on my phone (using Cordova run Android), I had a rude surprise – The app crashed on startup, showing a message before any HTML was visible:

Unfortunately, [Insert App Name Here] has stopped.

Thinking back to what I had done that might affect the configuration or interface layer, I eventually remembered that I had changed the name of the application. I had initially accepted project defaults, so undertook a small rebranding effort.

After a bit more digging and testing, I found that everything that could have broken is pretty much auto-generated from Cordova’s config.xml file when Android support is added to the project.  This includes java code bearing your application name, etc.

Rather than pick through that code, I simply removed Android support from the project, and re-added it.  Problem solved:

cordova platform remove android
cordova platform add android

Takehome point: Dev work in the browser is fine, but test any configuration changes on a phone or *real* emulator.

Running Your Android/Cordova App

Running the app

If this is your first Android/Cordova app, you’re going to find out pretty quickly (like in the first 5 minutes) that you’ll need a place to run it. You have a few options:

On Your Phone

It is easy to run the app on your own Android phone.  I’m listing this option first because it is reasonably quick, and quicker than the SDK emulator.   First go to Settings, turn on Developer Options and make sure USB Debugging is checked.    You can then connect to your development machine with a USB and execute:

cordova run android

Android SDK Emulators/Virtual Devices

The Android SDK comes with a variety of emulators, none of which are initially installed.  First run the Android Virtual Devices manager:

android avd

Here you can create one or more Virtual Devices which emulate different phones.  Add a new device and configure it.   Virtual devices can also be downloaded from smart phone manufacturers, if you want to be specific in your testing.  The following command will build and run your app on the virtual device:

cordova emulate android

Of course you can specify which virtual device you want to start up, or use the default.

This emulator is probably the most accurate representation of a phone that you can run, without actually installing on a real one.   But it is very slow to start up and run.  I found myself looking for an alternative almost immediately.

Just Use the Browser

It quickly occurred to me that because I was writing an HTML/JavaScript app (Cordova) that I could just run it in the browser.  So I chose my favorite node.js based lightweight server and was up and running in a few minutes.  This worked fine for basic functionality, and of course is very handy for addressing JavaScript issues.  This is the world I expect to work in, for most of my app’s functionality, because I’m using Ionic/AngularJS.

However this approach has a few disadvantages:  it’s not going to win any awards for appearance – I could not use this method for a client demo or screen shots.   And worst of all, it is ONLY for web functionality.

Also, I ran into an issue which didn’t show up here.  Of course.  It’s not running any of the Cordova scaffolding, so the minute you start changing configuration settings or want to check permissions, you need something more “real”.   I expect this method to break if I write custom extensions or need other features that chrome does not offer.

Ripple

I have used Ripple on a limited basis, but it appears to be a nice compromise between the incredibly slow SDK emulator and the “just run in browser” approach.

If you’re new to Ripple, here’s the 30 second low-down: It runs within Chrome, but you start it from the command line.   Do NOT install the chrome extension.  Just install it from npm as follows:

npm install -g ripple-emulator

You’ll still need to run in your phone or the SDK emulator, and I suggest doing so whenever you make a change to the configuration or underlying Java.  For example, the renaming problem I encountered did not show up in Ripple, though it did in the SDK emulator.

In summary, I expect you’ll be looking at your app using a variety of methods, depending on what you’re testing or need to do.    Let me know if I’ve left anything out, and good luck!

AngularJS/Cordova mobile app Dev Setup – Part 2

In AngularJS/Cordova mobile app Dev Setup – Part 1 we got things rolling.  We now have node.js, Cordova and associated tools in our environment.  Now to apply AngularJS and Ionic.

Part 1 will get you up and running with Cordova, from scratch.
Part 2 adds AngularJS and Ionic to the mix
Part 3 running your app

Ionic is a package of components including AngularJS, custom Angular directives, and some styles which give our HTML5 nice “mobile” look.   Out of the box, I found that my app was fairly true to the “iphone look”. Although I’m writing an Android application, this app might eventually be used for iPhone, so this suits me fine.

Ionic

Ionic is very simple to get started.  We’re just going to assume that you followed the steps in part 1.

  • Install Ionic
  • npm install -g ionic
  • Create an Ionic app
    ionic start myApp

Now you’re ready to run the sample code.   This can be done using Cordova to deploy to the emulator or your phone.  This code is also simple enough that you can also run it in a browser!

You can use Express or another lightweight brower to do this, by opening up the www directory [TODO: more detail].

Going the next step

You now have a start on your project, but there are still some tools that you will probably need.   I ended up adding these tools shortly after creating it, so here they are:

  • Create a package.json file.
  • Grunt, a scripting framework for node.js.  First you’ll need the command line (installed gloablly), and then install to the project (saving to json file)
    npm install -g grunt-cli
    npm install grunt --save-dev
  • Bower, a packaging manager, similar to npm but intended to manage external tools used in your app, rather than in your dev environment.
     npm install -g bower

Many bower packages require you to install git as well (for example, angular-google-maps).  When  installing for windows, I selected the “add to path” option.  This makes it possible to run bower from a standard MS-DOScommand line (you don’t have to use git/bash).

Enjoy Angular

Now it’s time to poke around with Angular and add some functionality.  Good luck!