14. Building a Cordova Application

At this point in the book, I’ve shown you a lot about Cordova programming, but I’ve not done a complete walkthrough of the process using a real application as an example. So, in this chapter, I take a complete application and show you the steps needed to create and test the application from start to finish.

About the Application

In PhoneGap Essentials, I created complete sample applications for each PhoneGap API; it was a great way to highlight all of the options for each API. For this chapter, I took one of the applications from that book and updated it for Cordova 3.0. I decided to use one of the applications I created to demonstrate how to implement a Compass watch.

The application displays a simple screen showing an image representing a compass dial. As the user rotates the device along a horizontal axis, the compass image rotates, and the heading is displayed on the page below the compass image. The original application’s screen is shown in Figure 14.1.

Image

Figure 14.1 Sample Compass Application

For this chapter, I thought I’d enhance the application’s UI using jQuery Mobile plus make use of the Cordova merges folder capability to use a different compass graphic for Android and iOS.

Creating the Application

To create the application, I opened a terminal window and navigated to my system’s dev folder. Next, I issued the following commands to create the Cordova project:

cordova create compass
cd compass
cordova platform add android ios

At this point, what I have is a new Cordova project with support for the Android and iOS mobile device platforms. I know that the application is going to need to leverage at least one Cordova core plugin, perhaps more, so to make it easy to remember the correct syntax for adding the core plugins, I opened a browser window and navigated to the Cordova command-line interface (CLI) guide at http://tinyurl.com/ob9hanq. If you go to that site and scroll down toward the middle of the page, you will see a listing containing the full command text for adding each of the Cordova plugins to a project, as shown in Figure 14.2.

Image

Figure 14.2 Cordova Documentation: The Command-Line Interface Guide

Knowing that I have to debug the application and therefore must write to the console, I copied the appropriate command from the guide, shown in Figure 14.2, then added the console plugin to the project using the following command:

cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-console.git

Since the application will be using the compass, I also added the compass plugin using the following command:

cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-device-orientation.git

Remember that Cordova doesn’t really show you anything as it works unless you turn on the –d flag. So, the complete terminal window output looks like the following:

Last login: Mon Sep 9 08:27:24 on ttys000
jmw-mini:~ jwargo$ cordova create compass
jmw-mini:~ jwargo$ cd compass
jmw-mini:compass jwargo$ cordova platform add android ios
jmw-mini:compass jwargo$ cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-
device-orientation.git
jmw-mini:compass jwargo$ cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-
plugin-console.git
jmw-mini:compass jwargo$

Next, I copied the index.html from the sample project I’m using and placed it in the project’s www folder. For your applications, you’ll simply open your text or HTML editor of choice and start coding.

The original version of this application had a very simple look to it, which was represented by the following HTML code:

<body onload="onBodyLoad()">
  <h1>Example 13-2</h1>
  <img src="compass.png" id="compass" />
  <br />
  <p id="headingInfo"></p>
</body>

I wanted to spruce up this version of the application a little bit, so I decided to use jQuery Mobile to create a more mobile phone like look for the app. The first thing I did was download jQuery (www.jquery.com) and jQuery Mobile (www.jquerymobile.com) from their respective websites. I copied the files over to the Cordova project’s www folder, then added the following lines to the head portion of the application’s index.html:

<link rel="stylesheet" href="jquery.mobile-1.3.2.min.css" />
<script type="text/javascript" charset="utf-8"
  src="jquery-2.0.3.min.js"></script>
<script type="text/javascript" charset="utf-8"
src="jquery.mobile-1.3.2.min.js"></script>

The application’s ability to rotate the image is provided by a free plugin to jQuery called jQuery Rotate; you can find information about the plugin at http://code.google.com/p/jqueryrotate. To add the plugin, I downloaded the plugin’s JavaScript file to the Cordova project’s www folder, then added the following line immediately after the other script tags in the application’s index.html file:

<script type="text/javascript" charset="utf-8"
  src="jQueryRotateCompressed.js"></script>

With the jQuery files in place, I updated the body section of the index.html to the following:

<body onload="onBodyLoad()">
  <div data-role="page">
    <div data-role="header">
      <h1>Compass</h1>
    </div>
    <div data-role="content">
      <div style="text-align : center;">
        <img src="images/compass.png" id="compass" />
        <br />
        <p id="headingInfo">
          <b>Heading:</b> 0 Degrees
        </p>
      </div>
    </div>
    <div data-role="footer" data-position="fixed">
      <h3>cordovaprogramming.com</h3>
    </div>
  </div>
</body>

jQuery Mobile uses data-role attributes within elements to style those particular elements with the appropriate look. So, in the preceding example, I created a header div on the page and assigned it a data-role of header and a footer div on the page and assigned it a data-role of footer. The data-position="fixed" forces the footer to remain on the bottom of the page.

Next, I moved the content for the page into a div assigned to a data-role of content. In order to have the compass graphic and heading text centered on the page, I added a new div and styled it with a style="text-align : center;", as shown in the code.


Note

You may be asking why I didn’t simply add the style the attribute to the div with the data-role of content. I probably could have done so, but I was trying to stay focused on the educational aspect of this sample and didn’t want to clutter things up too much.


With that code in place, the look of the application is changed dramatically, as shown in Figure 14.3.

Image

Figure 14.3 The Compass Application with a jQuery Mobile Look

Now that I have the application’s user interface updated, it’s time to start looking at the application’s JavaScript code. In the application’s index.html is a script tag that defines a few functions that are used by the application.

The first thing you will see within the script tag is the assignment of a function to the window.onerror event. Since I know that my code isn’t going to work right away (code never works the first time, does it?), and since a Cordova application fails silently most of the time, I added this assignment and the associated function to allow me to see information about every error that occurs when the application runs. The function, shown next, essentially receives an error, the name of the application file URL that encountered the error, and the line number that caused the error; then it generates the appropriate error message and writes it to the log and displays it in a dialog.

//Fires whenever there is an error
window.onerror = function(msg, url, line) {
  var resStr;
  var index = url.lastIndexOf('/'),
  if (index > -1) {
    url = url.substring(index + 1);
  }
  resStr = 'ERROR in ' + url + ' on line ' + line + ': ' + msg;
  console.error(appName + resStr);
  alert(resStr);
  return false;
};

Next comes the onDeviceReady function I’ve shown throughout the book. It is set up as an event listener for the Cordova deviceready event and fires after the Cordova container has finished initialization. In this function, I know that the Cordova container is ready, so I can do almost anything I want to do. Here’s the function:

function onDeviceReady() {
  console.log('onDeviceReady fired.'),
  hi = document.getElementById('headingInfo'),
  //Setup the watch
  //Read the compass every second (1000 milliseconds)
  var watchOptions = {
    frequency : 1000
  };
  console.log(appName + 'Creating watch: ' +
    JSON.stringify(watchOptions));
  watchID = navigator.compass.watchHeading(onSuccess, onError,
    watchOptions);
}

The first thing the function does is define a hi variable, which points to the page element that is assigned an ID of headingInfo. The variable is used later to replace the content within that element to show the device’s current heading.

Next, the function defines a watchOptions variable, which is used to help set up a heading watch that will cause the application to update the heading periodically. The watchOptions object can have either a frequency property or a filter property assigned to it. Right now, the application is set up with the following:

var watchOptions = {
  frequency : 1000
};

When used with a heading watch, it will cause the Compass API to report the device’s heading every 1000 milliseconds (every 1 second). The filter property is used to define the amount of heading change (in degrees) before the Compass API will report a heading. In the following example, the filter property instructs the Compass API to deliver a heading every time the heading changes more than 1 degree:

var watchOptions = {
  filter : 1
};

You can specify both properties as shown here, but as soon as you specify a filter, the frequency property is ignored by the Compass API:

var watchOptions = {
  frequency : 1000,
  filter : 1
};

With watchOptions defined, the function makes a call to watchHeading to set up the heading watch:

watchID = navigator.compass.watchHeading(onSuccess, onError,
  watchOptions);

As with all of the other Cordova APIs you’ve learned so far, the onSuccess function is executed whenever the Compass API sends a heading value, and the onError function is executed whenever an error is encountered by the Compass API.

Whenever the onSuccess function is executed, the Compass API passes it a heading object, which contains properties indicating the device’s current heading, as shown here:

{
  "magneticHeading":0,
  "trueHeading":0,
  "headingAccuracy":0,
  "timestamp":1378738354661
}

The onSuccess function uses the magneticHeading property of the heading object to determine the current heading, then uses that value to rotate the compass graphic by that number of degrees, as shown in the following function:

function onSuccess(heading) {
  console.log(appName + 'Received Heading'),
  console.log(appName + JSON.stringify(heading));
  var hv = Math.round(heading.magneticHeading);
  console.log(appName + 'Rotating to ' + hv + ' degrees'),
  $("#compass").rotate(-hv);
  hi.innerHTML = '<b>Heading:</b> ' + hv + ' Degrees';
}

Notice that the compass graphic is being rotated in the opposite direction of the device’s current heading. That is because, since the device is rotated, the compass graphic has to rotate in the opposite direction so that the North point on the compass graphic always points to Magnetic North.

Lastly, the application’s onError function is executed whenever the watch encounters an error. The function uses an error object passed to the function to identify the cause of the error and display an appropriate error message for the user. Notice that the onError function also cancels the watch, as it makes little sense to continue watching the heading when the application is not able to measure the heading.

function onError(err) {
  console.error(appName + 'Heading Error'),
  console.error(appName + 'Error: ' +  JSON.stringify(err));
  //Remove the watch since we're having a problem
  navigator.compass.clearWatch(watchID);
  //Clear the heading value from the page
  hi.innerHTML = '<b>Heading: </b>None';
  //Then tell the user what happened.
  if (err.code == CompassError.COMPASS_NOT_SUPPORTED) {
    alert('Compass not supported.'),
  } else if (compassError.code ==
    CompassError.COMPASS_INTERNAL_ERR) {
    alert('Compass Internal Error'),
  } else {
    alert('Unknown heading error!'),
  }
}

Notice that I’m writing to the console much more than I have for most of the other applications I’ve highlighted in the book. I’m doing this in order to show you a more real-life example of how I build Cordova applications. I use the console a lot to write out all of the application’s objects whenever I encounter them; this allows me to better understand what I’m getting from the application so I can more easily troubleshoot problems when they occur.

In my application, I defined an appName object:

var appName = "Compass - ";

Whenever the application writes to the console, I append the value for appName to the beginning of every entry:

console.error(appName + 'some message'),

When debugging applications for Android, you can use the monitor application to view the log entries in real time. As shown in Figure 14.4, you can then filter on the value passed in appName to see only console messages for the application.

Image

Figure 14.4 Android Developer Tools LogCat Window

As soon as the application is tested and found to be acceptable, I remove or comment out many of the places where the application writes to the console.

Listing 14.1 shows the complete contents of the application’s index.html.

Listing 14.1: index.html


<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-type" content="text/html;
      charset=utf-8">
    <meta name="viewport" id="viewport"
      content="width=device-width, height=device-height,
        initial-scale=1.0, maximum-scale=1.0, user-scalable=no;" />
    <title>Compass</title>
    <link rel="stylesheet" href="jquery.mobile-1.3.2.min.css" />
    <script type="text/javascript" charset="utf-8"
      src="jquery-2.0.3.min.js"></script>
    <script type="text/javascript" charset="utf-8"
      src="jquery.mobile-1.3.2.min.js"></script>
    <script type="text/javascript" charset="utf-8"
      src="jQueryRotateCompressed.js"></script>
    <script type="text/javascript" charset="utf-8"
      src="cordova.js"></script>
    <script type="text/javascript" charset="utf-8">
      //Some variables used by the application
      var hi, watchID;
      var appName = "Compass - ";

      //Fires whenever there is an error - helps with
      //toubleshooting.
      window.onerror = function(msg, url, line) {
        var resStr;
        var idx = url.lastIndexOf('/'),
        if (idx > -1) {
          url = url.substring(idx + 1);
        }
        resStr = 'ERROR in ' + url + ' on line ' + line + ': ' +
          msg;
        console.error(resStr);
        alert(resStr);
        return false;
      };

      function onBodyLoad() {
        document.addEventListener('deviceready', onDeviceReady,
          false);
      }

      function onDeviceReady() {
        console.log('onDeviceReady fired.'),
        hi = document.getElementById('headingInfo'),
        //Setup the watch
        //Read the compass every second (1000 milliseconds)
        var watchOptions = {
          frequency : 1000,
          filter : 1
        };
        console.log(appName + 'Creating watch: ' +
          JSON.stringify(watchOptions));
        watchID = navigator.compass.watchHeading(onSuccess,
          onError, watchOptions);
      }

      function onSuccess(heading) {
        console.log(appName + 'Received Heading'),
        console.log(appName + JSON.stringify(heading));
        var hv = Math.round(heading.magneticHeading);
        console.log(appName + 'Rotating to ' + hv + ' degrees'),
        $("#compass").rotate(-hv);
        hi.innerHTML = '<b>Heading:</b> ' + hv + ' Degrees';
      }

      function onError(err) {
        console.error(appName + 'Heading Error'),
        console.error(appName + 'Error: ' +
          JSON.stringify(err));
        //Remove the watch since we're having a problem
        navigator.compass.clearWatch(watchID);
        //Clear the heading value from the page
        hi.innerHTML = '<b>Heading: </b>None';
        //Then tell the user what happened.
        if (err.code == CompassError.COMPASS_NOT_SUPPORTED) {
          alert('Compass not supported.'),
        } else if (compassError.code ==
          CompassError.COMPASS_INTERNAL_ERR) {
          alert('Compass Internal Error'),
        } else {
          alert('Unknown heading error!'),
        }
      }
    </script>
  </head>
  <body onload="onBodyLoad()">
    <div data-role="page">
      <div data-role="header">
        <h1>Compass</h1>
      </div>
      <div data-role="content">
        <div style="text-align : center;">
          <img src="images/compass.png" id="compass" />
          <br />
          <p id="headingInfo">
            <b>Heading:</b> 0 Degrees
          </p>
        </div>
      </div>
      <div data-role="footer" data-position="fixed">
        <h3>cordovaprogramming.com</h3>
      </div>
    </div>
  </body>
</html>


Using Merges

One of the things I haven’t done yet is show you how to use the merges capability of the Cordova CLI. In Chapter 6, “The Mechanics of Cordova Development,” I explained how it works, but here I’ll show you an example.

The compass graphic in my original application was pretty lame; I created it in Microsoft Visio and simply wanted a circle with an arrow pointing north. I got what I wanted, but professional grade it wasn’t. For this upgraded version of the application, I wanted something with more panache and found a graphic that, while not technically accurate for my compass, implied a compass theme, so I decided to use it. To leverage the merges aspect of the CLI, I decided to make two versions of the image, one for Android and the other for iOS, using graphics from the PhoneGap website plunked into the center of the compass face. You can see the Android version in Figure 14.5 and the iOS version in Figure 14.6.

Image

Figure 14.5 Updated Compass Image for Android

Image

Figure 14.6 Updated Compass Image for iOS

Once I had the different graphics, it was time to use them to create different versions of the application using the CLI. Notice in the application’s code that the compass image is being pulled from images/compass.png. To allow me to use the CLI and merges, I had to create an images folder in the Cordova project’s merges/android folder and merges/ios folder. With those folders in place, all I had to do was copy the right image files to the right folders, making sure they both have the same file name. So, assuming my project is in a compass folder, I copied the Android image into compass/merges/android/images/compass.png and the iOS version into compass/merges/ios/images/compass.png.

During the build process, for the Android application, the Cordova CLI copies the file located at compass/merges/android/images/compass.png to compass/platforms/android/assets/www/images/compass.png. For the iOS application, the CLI copies the file located at compass/merges/ios/images/compass.png to compass/platforms/ios/www/images/compass.png.

The result is that I can still maintain one set of code and switch in the right resource files only as needed, depending on the platform. You can see the results of this process in the next section. The use case I used here isn’t perfect, but hopefully you can see that, if you’ve themed your application differently for different platforms, this capability provides you with the means to easily manage the different resources for each platform.

Testing the Application

To test the application, I can’t use the device simulators because compass emulation isn’t a standard feature of most simulators. When you try to run the application on an iOS simulator, for example, you will see an error message similar to the one shown in Figure 14.7.

Image

Figure 14.7 Compass Application Running on an iOS Simulator

Instead, I have to test on real devices. To test on an Android device, I connected my Motorola Atrix 2 to the computer using a USB cable, then issued the following command in a terminal window pointing to the Cordova project folder:

cordova run android

The Cordova CLI will do its stuff, and 30 seconds to a minute later, the application will launch on the device. Figure 14.8 shows the application running; I’ve cropped the image to cut away the blank portion of the application screen.

Image

Figure 14.8 Compass Application Running on an Android Emulator

You should be able to run the application on an iOS device using the CLI, but for some reason, even though it is supposed to work, it’s not currently supported, as shown in Figure 14.9. For iOS, emulate currently works; run does not.

Image

Figure 14.9 Cordova CLI Error Running Compass on an iOS Device

To run the application on an iOS device, you must first connect the device to the computer system, then open Xcode and open the Cordova project. With the project loaded, you can run the application as you would run any other application. When the application loads, it will display a screen similar to the one shown in Figure 14.10; I’ve cropped the image to cut away the blank portion of the application screen.

Image

Figure 14.10 Compass Application Running on an iOS Device

Notice that the compass graphics are different between the two devices, although I didn’t code anything to make this happen; this is only one of the cool ways the Cordova CLI enhances cross-platform development.

Wrap-Up

This chapter wraps up the Cordova programming story by demonstrating how to do cross-platform Cordova development from start to finish. In this chapter, you saw how to create a single application that uses different resources depending on the mobile platform running the application. I also showed you a complete Cordova web application and described each component. You’ve also seen more details about how to use the Compass API in your applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset