TicketMonster Tutorial

Creating hybrid mobile versions of the application with Apache Cordova

What will you learn here?

You finished creating the front-end for your application, and it has mobile support. You would now like to provide native client applications that your users can download from an application store. After reading this tutorial, you will understand how to reuse the existing HTML5 code for create native mobile clients for each target platform with Apache Cordova.

You will learn how to:

  • make changes to an existing web application to allow it to be deployed as a hybrid mobile application;

  • create a native application for Android with Apache Cordova;

  • create a native application for iOS with Apache Cordova;

What are hybrid mobile applications?

Hybrid mobile applications are developed in HTML5 - unlike native applications that are compiled to platform-specific binaries. The client code - which consists exclusively of HTML, CSS, and JavaScript - is packaged and installed on the client device just as any native application, and executes in a browser process created by a surrounding native shell.

Besides wrapping the browser process, the native shell also allows access to native device capabilities, such as the accelerometer, GPS, contact list, etc., made available to the application through JavaScript libraries.

In this example, we use Apache Cordova to implement a hybrid application using the existing HTML5 mobile front-end for TicketMonster, interacting with the RESTful services of a TicketMonster deployment running on JBoss A7 or JBoss EAP.

ticket_monster_hybrid
Figure 1. Architecture of hybrid TicketMonster

Tweak your application for remote access

Before we make the application hybrid, we need to make some changes in the way in which it accesses remote services. Note that the changes have already been implemented in the user front end, here we show you the code that we needed to modify.

In the web version of the application the client code is deployed together with the server-side code, so the models and collections (and generally any piece of code that will perform REST service invocations) can use URLs relative to the root of the application: all resources are serviced from the same server, so the browser will do the correct invocation. This also respects the same origin policy enforced by default by browsers, to prevent cross-site scripting attacks.

If the client code is deployed separately from the services, the REST invocations must use absolute URLs (we will cover the impact on the same-origin policy later). Furthermore, since we want to be able to deploy the application to different hosts without rebuilding the source, it must be configurable.

You already caught a glimpse of this in the user front end chapter, where we defined the configuration module for the mobile version of the application.

src/main/webapp/resources/js/configurations/mobile.js
...
define("configuration", function() {
    if (window.TicketMonster != undefined && TicketMonster.config != undefined) {
        return {
            baseUrl: TicketMonster.config.baseRESTUrl
        };
    } else {
        return {
            baseUrl: ""
        }
    }
})
...

This module has a baseURL property that is either set to an empty string for relative URLs or to a prefix, such as a domain name, depending on whether a global variable named TicketMonster has already been defined, and it has a baseRESTUrl property.

All our code that performs REST services invocations depends on this module, thus the base REST URL can be configured in a single place and injected throughout the code, as in the following code example:

src/main/webapp/resources/js/app/models/event.js
/**
 * Module for the Event model
 */
define([
    'configuration',
    'backbone'
], function (config) {
    /**
     * The Event model class definition
     * Used for CRUD operations against individual events
     */
    var Event = Backbone.Model.extend({
        urlRoot: config.baseUrl + 'rest/events' // the URL for performing CRUD operations
    });
    // export the Event class
    return Event;
});

The prefix is used in a similar fashion by all the other modules that perform REST service invocations. You don’t need to do anything right now, because the code we created in the user front end tutorial was written like this originally. Be warned, if you have a mobile web application that uses any relative URLs, you will need to refactor them to include some form of URL configuration.

Downloading Apache Cordova

The next step is downloading and installing Apache Cordova. Download the distribution from http://phonegap.com/download and unzip it.

cordova-distro-structure
Figure 2. Apache Cordova distribution

While migrating TicketMonster, we will work with the files in the lib directory. They contain native libraries for each of the supported platforms, as well JavaScript libraries . We have highlighted the contents of the android folder. The folders for the other platforms have similar content.

Creating an Android hybrid mobile application

Tip
What do you need for Android?

For building the Android version of the application you need to install the Android Developer Tools, which require an Eclipse instance (3.6.2 or later), and can run on Windows (XP, Vista, 7), Mac OS X (10.5.8 or later), Linux (with GNU C Library - glibc 2.7 or later, 64-bit distributions having installed the libraries for running 32-bit applications).

Creating an Android project using Apache Cordova

First, we will create an Android project in JBDS. The prerequisites for that are having the Android SDK installed locally , as well as the Android (ADT) plugin for Eclipse installed in JBDS.

For the former, download the Android SDK from http://developer.android.com/sdk/index.html and unzip it in a directory of your choice, remembering its location.

For the latter, select Help → Install New Software from the menu, using the URL https://dl-ssl.google.com/android/eclipse/ and selecting the Developer Tools option. Restart Eclipse.

Now we can create a new Android project.

  1. Select File → New → Other and selecting Android Application Project.

  2. Enter the project information: application name, project name, package.

    Application Name

    TicketMonster

    Project Name

    TicketMonster

    package

    org.jboss.jdf.ticketmonster.android

    android-app-project-package
    Figure 3. Entering the application name, project name and package
  3. Select default values for the next couple of screens (Configure New Project, Launcher icon).

  4. Select BlankActivity as the activity type.

    android-activity-type
    Figure 4. Select activity type
  5. Name the newly created activity TicketMonsterActivity.

    android-activity-name
    Figure 5. Name the new activity

A final step involves adding the Apache Cordova library to the project. Copy the lib/android/cordova-2.7.0.jar file from the Cordova distribution into the libs folder of the project.

add-cordova-jar
Figure 6. Add the Cordova jar

Once you have finished creating the project, navigate to the assets directory. Now we need to create a www directory, that will contain the HTML5 code of the application. Since we are reusing the TicketMonster code you can simply create a symbolic link to the webapp directory of TicketMonster. Alternatively, you can copy the code of TicketMonster and make all necessary changes there (however, in that case you will have to maintain the code of the application).

$ ln -s $TICKET_MONSTER_HOME/demo/src/main/webapp www

Inside the Android project, modify permissions and additional configurations to AndroidManifest.xml to look as follows

AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.jboss.jdf.ticketmonster.android"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />

    <supports-screens
        android:anyDensity="true"
        android:largeScreens="true"
        android:normalScreens="true"
        android:resizeable="true"
        android:smallScreens="true" />

    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.BROADCAST_STICKY" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale">
        <activity
            android:name=".TicketMonsterActivity"
            android:label="@string/title_activity_ticket_monster" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

We also need to copy the xml directory containing the Cordova project configuration file - config.xml, from the Cordova distribution to the res directory of the project.

add-cordova-config
Figure 7. Add the Cordova project configuration file

We will add our REST service URL to the domain whitelist in the config.xml file (you can use "*" too, for simplicity, during development) :

res/xml/config.xml
<?xml version="1.0" encoding="utf-8"?>
<cordova>

        ...

    <!--
    access elements control the Android whitelist.
    Domains are assumed blocked unless set otherwise
     -->

    <access origin="http://localhost"/> <!-- allow local pages -->
    <access origin="http://ticketmonster-jdf.rhcloud.com"/>

    ...

</cordova>

Finally, we will update the Android TicketMonsterActivity class, the entry point of our Android application.

src/org/jboss/jdf/ticketmonster/android/TicketMonsterActivity.java
package org.jboss.jdf.ticketmonster.android;

import org.apache.cordova.DroidGap;

import android.os.Bundle;
import android.webkit.WebSettings;

public class TicketMonsterActivity extends DroidGap {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.loadUrl("file:///android_asset/www/index.html");
    }

    @Override
    public void init() {
     super.init();

     WebSettings settings = this.appView.getSettings();
     settings.setUserAgentString("TicketMonster Cordova Webview Android");
    }

}

Note how we customize the user agent information for the wrapped browser. This will allow us to identify that the application runs in Cordova, on an Android platform, which will be useful later on.

Adding Apache Cordova to TicketMonster

First, we will copy the Apache Cordova JavaScript library to the project. From the directory where you unzipped the distribution, copy the lib\android\cordova-2.7.0.js file to the src/main/webapp/resources/js/libs folder, renaming it to cordova-android-2.7.0.js, to avoid naming conflicts with other platforms (such as iOS which we will also implement as part of this tutorial).

Next, we need to load the library in the application. We will create a separate module, that will load the rest of the mobile application, as well as the Apache Cordova JavaScript library for Android. We also need to configure a base URL for the application. For this example, we will use the URL of the cloud deployment of TicketMonster.

src/main/webapp/resources/js/libs/hybrid-android.js
// override configuration for RESTful services
var TicketMonster = {
    config:{
        baseRESTUrl:"http://ticketmonster-jdf.rhcloud.com/"
    }
}

require (["resources/js/libs/cordova-android-2.7.0.js","mobile"], function() {

});

The final step will involve adjusting src/main/webapp/resources/js/configurations/loader.js to load this module when running on Android, using the user agent setting we have already configured in the project.

src/main/webapp/resources/js/configurations/loader.js
//detect the appropriate module to load
define(function () {

    /*
     A simple check on the client. For touch devices or small-resolution screens)
     show the mobile client. By enabling the mobile client on a small-resolution screen
     we allow for testing outside a mobile device (like for example the Mobile Browser
     simulator in JBoss Tools and JBoss Developer Studio).
     */

    var environment;

    if (navigator.userAgent.indexOf("TicketMonster Cordova Webview Android") > -1) {
        environment = "hybrid-android"
    }
    else if (Modernizr.touch || Modernizr.mq("only all and (max-width: 480px)")) {
        environment = "mobile"
    } else {
        environment = "desktop"
    }

    require([environment]);
});

Now you are ready to run the application. Right-click on project Run asAndroid Application.

Creating an iOS hybrid mobile application

In order to create the iOS hybrid mobile version of the application make you sure you have the following software installed:

  • Xcode 4.5+

  • XCode Command Line Tools

Note
You need a Mac OS X for this

Creating the iOS hybrid mobile version of the application requires a system running Mac OS X Lion or later (10.7+), mainly for running Xcode.

Also, we assume that you have installed and extracted Apache Cordova already as described in a previous section.

Creating an iOS project using Apache Cordova

First, we need to create an iOS project. In order to do so we run the create command, to be found in the lib/ios/bin of your Apache Cordova distribution. Run the command with the following parameters:

$ $LIB_IOS_BIN/create $TICKET_MONSTER_HOME/cordova/ios org.jboss.ticketmonster.cordova.ios TicketMonster

For the purpose of this tutorial, we assume that the cordova directory which is the parent of the ios directory where the project is created, is at the same level as the directory where the original project exists.

Note

The create script for Cordova/iOS will create a www sub-directory in the ios directory. This www sub-directory will need to be deleted since we’re sharing the sources from the TicketMonster project.

Delete the www sub-directory under the TicketMonster, since we will not be using the underlying sources

$ rm -rf $TICKET_MONSTER_HOME/cordova/ios/www

We then create a symbolic link inside the ios directory to the original TicketMonster project, with the name www.

$ ln -s $TICKET_MONSTER_HOME/demo/src/main/webapp www

Now we open the created project in Xcode.

Just as in the case of the Android application, we customize the user agent information that gets passed on to the browser. We will use this information to load the proper JavaScript library. So we will adjust the initialize method in the generated code to that effect.

Classes/AppDelegate.m
...

+ (void)initialize {
    // Set user agent
    NSDictionary *dictionary = [[NSDictionary alloc]
                      initWithObjectsAndKeys:@"TicketMonster Cordova Webview iOS", @"UserAgent", nil];
    [[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
    [dictionary release];
}

...

The Cordova library for iOS is already included in the generated project.

Adding Apache Cordova for iOS to TicketMonster

First, we copy the Apache Cordova JavaScript library to the project. From the directory where you unzipped the distribution, copy the lib\ios\CordovaLib\cordova.ios.js file to the src/main/webapp/resources/js/libs folder, renaming it to cordova-ios-2.7.0.js, to avoid naming conflicts with other platforms (such as Android which we already implemented as part of this tutorial.

Next, we need to load the library in the application. We will create a separate module, that will load the rest of the mobile application, as well as the Apache Cordova JavaScript library for iOS. We also need to configure a base URL for the application. For this example, we will use the URL of the cloud deployment of TicketMonster.

Note

The cordova.io.js is typically present as cordova-2.7.0.js in Cordova/iOS projects. The aforementioned Cordova create script renames the file during project creation to cordova-2.7.0.js. This is why we propose renaming it to avoid potential conflicts.

src/main/webapp/resources/js/libs/hybrid-ios.js
// override configuration for RESTful services
var TicketMonster = {
    config:{
        baseRESTUrl:"http://ticketmonster-jdf.rhcloud.com/"
    }
}

require (["resources/js/libs/cordova-ios-2.7.0.js","mobile"], function() {

});

Finally, we once again edit the JavaScript loader module to add support for iOS.

src/main/webapp/resources/js/configurations/loader.js
//detect the appropriate module to load
define(function () {

    /*
     A simple check on the client. For touch devices or small-resolution screens)
     show the mobile client. By enabling the mobile client on a small-resolution screen
     we allow for testing outside a mobile device (like for example the Mobile Browser
     simulator in JBoss Tools and JBoss Developer Studio).
     */

    var environment;

    if (navigator.userAgent.indexOf("TicketMonster Cordova Webview iOS") > -1) {
        environment = "hybrid-ios"
    }
    else if (navigator.userAgent.indexOf("TicketMonster Cordova Webview Android") > -1) {
        environment = "hybrid-android"
    }
    else if (Modernizr.touch || Modernizr.mq("only all and (max-width: 480px)")) {
        environment = "mobile"
    } else {
        environment = "desktop"
    }

    require([environment]);
});

Now you are ready to run the application. Select a simulator and run (Cmd-R).

Conclusion

This concludes our tutorial for building a hybrid application with Apache Cordova. You have seen how we have turned a working HTML5 web application into one that can run natively on Android and iOS.

For more details, as well as an example of deploying to a physical device, consult the Aerogear tutorial on the same topic.

Share the Knowledge

Find this guide useful?

Feedback

Find a bug in the guide? Something missing? You can fix it by [forking the repository](http://github.com/jboss-jdf/ticket-monster), making the correction and [sending a pull request](http://help.github.com/send-pull-requests). If you're just plain stuck, feel free to ask a question in the [user discussion forum](http://site-jdf.rhcloud.com/forums/jdf-users).

Recent Changelog

  • Jul 13, 2013: Forge-345 improved the styling for code listings Vineet Reynolds
  • Jul 10, 2013: Modified the tutorial generator to use asciidoctor Vineet Reynolds
  • Jun 24, 2013: Switching from setex to tax headers completely Rafael Benevides
  • Jun 07, 2013: Jdf-356 corrected instructions for configuring desktop, mobile and cordova environments Vineet Reynolds
  • May 25, 2013: Jdf-338 upgraded cordova usage from 2.0.0 to 2.7.0 Vineet Reynolds
  • Jan 24, 2013: Fix syntax errors that upset asciidoctor Dan Allen
  • Dec 07, 2012: Corrected step numbering Marius Bogoevici
  • Dec 07, 2012: Add version info for android Marius Bogoevici
  • Dec 07, 2012: Updates and fixes to hybrid Marius Bogoevici
  • Dec 06, 2012: Corrections to hybrid ui based on feedback on pr Marius Bogoevici
  • Nov 12, 2012: First draft of cordova tutorial Marius Bogoevici
  • May 29, 2012: Fix up burr's brain dump and remove spaces from file names Pete Muir
  • May 14, 2012: Move remaining files to asciidoc Pete Muir

See full history »