Book Image

Mastering PhoneGap Mobile Application Development

By : Kerri Shotts
Book Image

Mastering PhoneGap Mobile Application Development

By: Kerri Shotts

Overview of this book

PhoneGap is a useful and flexible tool that enables you to create complex hybrid applications for mobile platforms. In addition to the core technology, there is a large and vibrant community that creates third-party plugins that can take your app to the next level. This book will guide you through the process of creating a complex data-driven hybrid mobile application using PhoneGap, web technologies, and third-party plugins. A good foundation is critical, so you will learn how to create a useful workflow to make development easier. From there, the next version of JavaScript (ES6) and the CSS pre-processor SASS are introduced as a way to simplify creating the look of the mobile application. Responsive design techniques are also covered, including the flexbox layout module. As many apps are data-driven, you'll build an application throughout the course of the book that relies upon IndexedDB and SQLite. You'll also download additional content and address how to handle in-app purchases. Furthermore, you’ll build your own customized plugins for your particular use case. When the app is complete, the book will guide you through the steps necessary to submit your app to the Google Play and Apple iTunes stores.
Table of Contents (19 chapters)
Mastering PhoneGap Mobile Application Development
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

How to execute Cordova tasks


It's tempting to use the Cordova command-line interface directly, but there's a problem with this: there's no great way to ensure what you write works across multiple platforms. If you are certain you'll only work with a specific platform, you can go ahead and execute shell commands instead; but what we're going to do is a bit more flexible.

Note

The code in this section is inspired by https://github.com/kamrik/CordovaGulpTemplate.

The Cordova CLI is really just a thin wrapper around the cordova-lib project. Everything the Cordova CLI can do, cordova-lib does as well.

Because the Cordova project will be a build artifact, we need to be able to create a Cordova project in addition to building the project. We'll also need to emulate and run the app. To accomplish this, we'll need to create a new utility file named gulp/utils/cordova-tasks.js. At the top we require cordova-lib and other packages we'll need:

var cordovaLib = require("cordova-lib"),
    pkg = require("../../package.json"),
    config = require("../config"),
    path = require("path"),
    settings = require("../settings"),
    paths = require("../utils/paths");
var cordova = cordovaLib.cordova.raw;

Next, let's create the code to create a new Cordova project in the build directory:

var cordovaTasks = {
  // CLI: cordova create ./build com.example.app app_name
  //              --copy-from template_path
  create: function create() {
    return cordova.create(paths.makeFullPath(".", paths.DEST), 
      pkg.cordova.id, pkg.cordova.name,
      {
        lib: {
          www: {
            url: path.join(process.cwd(), pkg.cordova.template),
            link: false
          }
        }
      });
  }
}
module.exports = cordovaTasks;

Although it's a bit more complicated than cordova create is on the command line, you should be able to see the parallels. The lib object is passed simply to provide a template for the project (equivalent to --copy-from on the command line). In our case, package.json specifies that this should come from the blank/ directory in the code bundle of this book. If we don't do this, all our apps would be created with the sample Hello World app that Cordova installs by default.

Note

Our blank project template resides in ../blank relative to the project root. Yours may reside elsewhere (since you're apt to reuse the same template), so package.json can use whatever path you need. Or, you might want the template to be within your project's root; in which case, package.json should use a path inside your project's root directory.

We won't create a task to use this just yet. We need to define several other methods to build and emulate the Cordova app. First, we need to add some additional settings to gulp/settings.js:

var settings = { …,
  PLATFORM = gutil.env.platform ? gutil.env.platform :"ios",
  BUILD_MODE = gutil.env.mode ? gutil.env.mode :"debug",
  BUILD_PLATFORMS = (gutil.env.for ? gutil.env.for
                                   : "ios,android").split(","),
  TARGET_DEVICE = gutil.env.target ? "--target=" +
                  gutil.env.target : ""
}

Next, let's continue to add the additional methods we need to the cordovaTasks object:

var cordovaTasks = {
  create: function create() {
    /* as above */
  },
  cdProject: function cdProject() {
    process.chdir(paths.makeFullPath("www", paths.DEST));
  },
  cdUp: function cdUp() {
    process.chdir("..");
  },
  // cordova plugin add ...
  addPlugins: function addPlugins() {
    cordovaTasks.cdProject();
    return cordova.plugins("add", pkg.cordova.plugins)
      .then(cordovaTasks.cdUp);
  },
  // cordova platform add ...
  addPlatforms: function addPlatforms() {
    cordovaTasks.cdProject();
    function transformPlatform(platform) {
      return path.join(process.cwd(), "node_modules",
        "cordova-" + platform);
    }
    return cordova.platforms("add",
      pkg.cordova.platforms.map(transformPlatform))
      .then(cordovaTasks.cdUp);
  },
  // cordova build <platforms> --release|debug
  //                           --target=...|--device
  build: function build() {
    var target = settings.TARGET_DEVICE;
    cordovaTasks.cdProject();
    if (!target || target === "" ||
      target === "--target=device") {
      target = "--device";
    }
    return cordova.build({
      platforms: settings.BUILD_PLATFORMS,
      options: ["--" + settings.BUILD_MODE, target]
    }).then(cordovaTasks.cdUp);
  },
  // cordova emulate ios|android --release|debug
  emulate: function emulate() {
    cordovaTasks.cdProject();
    return cordova.emulate({
      platforms: [settings.PLATFORM],
      options: ["--" + settings.BUILD_MODE, 
        settings.TARGET_DEVICE]
    }).then(cordovaTasks.cdUp);
  },
  // cordova run ios|android --release|debug
  run: function run() {
    cordovaTasks.cdProject();
    return cordova.run({
      platforms: [settings.PLATFORM],
      options: ["--" + settings.BUILD_MODE, "--device",
        settings.TARGET_DEVICE]
    }).then(cordovaTasks.cdUp);
  },
  init: function() {
    return cordovaTasks.create()
      .then(cordovaTasks.copyConfig)
      .then(cordovaTasks.addPlugins)
      .then(cordovaTasks.addPlatforms);
  }
};

Note

If you aren't familiar with promises, you might want to learn more about them. http://www.html5rocks.com/en/tutorials/es6/promises/ is a fantastic resource.

Most of the previous tasks should be fairly self-explanatory; they correspond directly to their Cordova CLI counterparts. A few, however, need a little more explanation:

  • cdProject / cdUp: These change the current working directory. All the cordova-lib commands after create need to be executed from within the Cordova project directory, not our project's root directory. You should notice them in several of the tasks.

  • addPlatforms: The platforms are added directly from our project's dependencies, rather than from the Cordova CLI. This allows us to control the platform versions we are using. As such, addPlatforms needs to do a little more work to specify the actual directory name of each platform.

  • build: This executes the cordova build command. By default, CLI builds every platform. Since we will want to control the platforms that are built, hence we can use BUILD_PLATFORMS to control this behavior. On iOS, the build for an emulator is different than the build for a physical device. So, we also need a way to specify this, which is what TARGET_DEVICE does. This will look for emulators with the name specified for TARGET_DEVICE. But we might want to build for a physical device; in which case, we will look for device (or no target specified at all) and switch over to the --device flag which forces Cordova to build for a physical device.

  • init: This does the hard work of creating the Cordova project, copying the configuration file (and performing substitutions), adding plugins to the Cordova project, and then adding platforms.

Now is also a good time to mention that we can specify various settings with switches on the Gulp command line. In the earlier snippet, we're supporting the use of --platform to specify the platform to emulate or run, --mode to specify the build mode (debug or release), --for to determine what platforms Cordova will build for, and --target to specify the target device. The code will specify reasonable defaults if these switches aren't specified; but they also allow the developer extra control over the workflow, which is very useful. For example, we'll be able to use commands like the following:

$ gulp build --for ios,android --target device
$ gulp emulate --platform ios --target iPhone-6s
$ gulp run --platform ios --mode release

Next, let's write the code to actually perform various Cordova tasks. It isn't difficult, but we need to create a lot of files. Each file name in the code below is in comments:

// gulp/tasks/clean.js
var paths = require("../utils/paths"),
    config = require("../config"),
    rimraf = require("rimraf");
function clean(cb) {
    var BUILD_PATH = paths.makeFullPath(".", paths.DEST);
    rimraf(BUILD_PATH, cb);
}
module.exports = {
    task: clean
}

// gulp/tasks/init.js
var cordovaTasks = require("../utils/cordova-tasks");
module.exports = {
  deps: ["clean"],
  task: cordovaTasks.init
};

// gulp/tasks/build.js
var cordovaTasks = require("../utils/cordova-tasks");
module.exports = {
  deps: ["copy"],
  task: cordovaTasks.build
};

// gulp/tasks/emulate.js
var cordovaTasks = require("../utils/cordova-tasks");
module.exports = {
  deps: ["copy"],
  task: cordovaTasks.emulate
};

// gulp/tasks/run.js
var cordovaTasks = require("../utils/cordova-tasks");
module.exports = {
  deps: ["copy"],
  task: cordovaTasks.run
};

There's a catch with the init task: it will fail if anything is already in the build/ directory. As you can guess, this could easily happen; so we also created a clean task. This uses rimraf to delete a specified directory, which is equivalent to using rm -rf build. We then ensured that init depends on clean. So, whenever we execute gulp init, the old Cordova project is removed and a new one is created for us.

Finally, note that all the build (and other) tasks depend on copy. This means that all our files in src/ will be copied (and transformed, if necessary) to build/ prior to executing the desired Cordova command. As you can see, our tasks are already becoming very complex, while remaining comprehensible when they are taken singularly.

This means that we can now use the following tasks in Gulp:

$ gulp init                   # create the cordova project;
                              # cleaning first if needed
$ gulp clean                  # remove the cordova project
$ gulp build                  # copy src to build; apply
                              # transformations; cordova build
$ gulp build --mode release   # do the above, but build in
                              # release mode
$ gulp build --for ios        # only build for iOS
$ gulp build --target=device  # build device versions instead of
                              # emulator versions
$ gulp emulate --platform ios # copy src to build; apply
                              # transformations;
                              # cordova emulate ios
$ gulp emulate --platform ios --target iPhone-6
                              # same as above, but open the
                              # iPhone 6 emulator
$ gulp run --platform ios     # copy src to build;
                              # apply transformations;
                              # cordova run ios --device

Now, you're welcome to use the previous code as it is or you can use an NPM package that takes care of the cordovaTasks portion for you. This has the benefit of drastically simplifying your Gulp configuration. We've already included this package in our package.json file as well as our Gulp configuration. It's named cordova-tasks and was created by the author. It shares a lot of similarities to the earlier code. To see how it works (and how much simpler the tasks become), see logology-v01/gulp in the code package for this book.

This was one of the complex sections; so if you've come this far, take a coffee break. Next, we'll worry about managing app version numbers.