Three hooks your Cordova/PhoneGap project needs

November 12, 2013By 54 Comments

Hooks are pieces of code that Cordova CLI executes at certain points in your Cordova or PhoneGap application build. (Remember, PhoneGap and Cordova both use the Cordova CLI unless you are using PhoneGap Build.) They allow you to extend the Cordova CLI framework to suite your needs. While you can use hooks to integrate the Cordova CLI into a larger framework, like grunt, typically you’ll be writing project level hooks to manipulate files in your project.

Hooks live in the rootProject/hooks/ folder, in a subdirectory named after project command: before_xxx or after_xxx, where xxx is the project command (prepare, build, etc). The supported project commands are listed here.

Hooks can be written in any programming language, but they are executed whenever you build your project, on whatever computer you are building your project. So I’d stay away from writing hooks in C#, unless you are only developing on Windows. I have written hooks in shell script and in Node.js. Node.js is server side JavaScript, and since Cordova CLI is written in Node.js, it is a safe choice because it is guaranteed to be present.

Hooks do tie your build process to the Cordova CLI, and require your developers to have the entire CLI toolchain on their computers, so they might not be a good fit for every shop.

Below are three hooks that every project using Cordova CLI should use. All code was tested on using cordova 3.1.0-0.2.0 on a CentOS 6 virtual machine. These hooks are all available for download at http://www.mooreds.com/sample-hooks.tar.gz.

Add Plugins

With the new modular architecture of Cordova 3.x, every app needs plugins, even to use basic functionality such as logging or geolocation. Rather than document which plugins/features your project needs and ask each new developer to install them, download and install them automatically with a hook in the after_platform_add step. Using this plugin, every time a developer checks out the project and adds a platform, they automatically have the required plugins.

#!/usr/bin/env node

//this hook installs all your plugins

// add your plugins to this list--either 
// the identifier, the filesystem location 
// or the URL
var pluginlist = [
    "org.apache.cordova.device",
    "org.apache.cordova.device-motion",
    "org.apache.cordova.device-orientation",
    "org.apache.cordova.geolocation",
    "https://github.com/chrisekelley/AppPreferences/"
];

// no need to configure below

var fs = require('fs');
var path = require('path');
var sys = require('sys')
var exec = require('child_process').exec;

function puts(error, stdout, stderr) {
    sys.puts(stdout)
}

pluginlist.forEach(function(plug) {
    exec("cordova plugin add " + plug, puts);
});

(This hook is the 010_install_plugins.js file in the download.)

Replace Text Depending on Environment

You will often have development, staging and production environments for your Cordova application. Each of these environments can vary–they could depend on data from different servers, need different API keys, or even just have a different splash screen to help when testing (“STAGE app” vs “app”). While you could change each file in your app that had deployment specific information each time you want to build for a different environment, this is error prone and tedious. Such a wide ranging modification is better to automate.

To do so, start by creating a configuration file that has an object for each deployment environment, like this one:

{
"stage":
        {
        "datahostname" : "'qa-api.mydomain.com'"
        },
"prod":
        {
        "datahostname" : "'api.mydomain.com'"
        }
}

(There is a sample file project.json configuration file in the download as well.)

In each object, create key value pairs where the key is the type of item to be configured (like “datahostname” or “apikey”), and the value is the correct value for that particular environment (like “api.mydomain.com” or “AA3ACF”). Add tokens to files in your www directory for each environment dependent variable. Determine which environment you are building for with one master variable. Use a hook script to replace the variables in the files. This hook script should be added to the after_prepare hook directory so that it is run every time you build.

#!/usr/bin/env node

// this plugin replaces arbitrary text in arbitrary files
//
// Look for the string CONFIGURE HERE for areas that need configuration
//

var fs = require('fs');
var path = require('path');

var rootdir = process.argv[2];

function replace_string_in_file(filename, to_replace, replace_with) {
    var data = fs.readFileSync(filename, 'utf8');

    var result = data.replace(new RegExp(to_replace, "g"), replace_with);
    fs.writeFileSync(filename, result, 'utf8');
}

var target = "stage";
if (process.env.TARGET) {
    target = process.env.TARGET;
}

if (rootdir) {
    var ourconfigfile = path.join(rootdir, "config", "project.json");
    var configobj = JSON.parse(fs.readFileSync(ourconfigfile, 'utf8'));

    // CONFIGURE HERE
    // with the names of the files that contain tokens you want 
    // replaced.  Replace files that have been copied via the prepare step.
    var filestoreplace = [
        // android
        "platforms/android/assets/www/index.html",
        // ios
        "platforms/ios/www/index.html",
    ];
    filestoreplace.forEach(function(val, index, array) {
        var fullfilename = path.join(rootdir, val);
        if (fs.existsSync(fullfilename)) {
            // CONFIGURE HERE
            // with the names of the token values. For example, 
            // below we are looking for the token 
            // /*REP*/ 'api.example.com' /*REP*/ and will replace 
            // that token
            replace_string_in_file(fullfilename, 
                "/\\*REP\\*/ 'api.example.com' /\\*REP\\*/", 
                configobj[target].datahostname);
            // ... any other configuration options
        } else {
            //console.log("missing: "+fullfilename);
        }
    });

}

(This hook is the 020_replace_text.js file in the download.)

This hook makes building for production as easy as TARGET=prod cordova build ios.

Copy Icons and Splashscreens

Having custom icons and splashscreens is required for a professional mobile app. Unfortunately, Cordova CLI doesn’t support this key functionality yet. (Don’t be deceived by the icon images under www–they don’t do anything except increase the size of the final app and confuse newcomers). You can manually move of copy the icon and splash screen files to the proper directories each time you build. But if you want to version control your icons and splashscreens, or make the build replicable across machines, you need to automatically put the image resource files where the platform specific build processes expect them.

I like to store my icon/splash screen files under a top level directory (a peer to the www directory) and use an after_prepare hook to copy the files to their correct directories under platforms. It’s a bit tedious to figure out, but once you’ve done it once and written the hook, you are done (at least until Google, Apple, or another vendor introduces another size of icon you need to handle).

#!/usr/bin/env node

//
// This hook copies various resource files 
// from our version control system directories 
// into the appropriate platform specific location
//


// configure all the files to copy.  
// Key of object is the source file, 
// value is the destination location.  
// It's fine to put all platforms' icons 
// and splash screen files here, even if 
// we don't build for all platforms 
// on each developer's box.

var filestocopy = [{
    "config/android/res/drawable/icon.png": 
    "platforms/android/res/drawable/icon.png"
}, {
    "config/android/res/drawable-hdpi/icon.png": 
    "platforms/android/res/drawable-hdpi/icon.png"
}, {
    "config/android/res/drawable-ldpi/icon.png": 
    "platforms/android/res/drawable-ldpi/icon.png"
}, {
    "config/android/res/drawable-mdpi/icon.png": 
    "platforms/android/res/drawable-mdpi/icon.png"
}, {
    "config/android/res/drawable-xhdpi/icon.png": 
    "platforms/android/res/drawable-xhdpi/icon.png"
}, {
    "config/android/res/drawable/splash.png": 
    "platforms/android/res/drawable/splash.png"
}, {
    "config/android/res/drawable-hdpi/splash.png": 
    "platforms/android/res/drawable-hdpi/splash.png"
}, {
    "config/android/res/drawable-ldpi/splash.png": 
    "platforms/android/res/drawable-ldpi/splash.png"
}, {
    "config/android/res/drawable-mdpi/splash.png": 
    "platforms/android/res/drawable-mdpi/splash.png"
}, {
    "config/android/res/drawable-xhdpi/splash.png": 
    "platforms/android/res/drawable-xhdpi/splash.png"
}, {
    "config/ios/Resources/icons/icon-72.png": 
    "platforms/ios/YourAppName/Resources/icons/icon-72.png"
}, {
    "config/ios/Resources/icons/icon.png": 
    "platforms/ios/YourAppName/Resources/icons/icon.png"
}, {
    "config/ios/Resources/icons/icon@2x.png": 
    "platforms/ios/YourAppName/Resources/icons/icon@2x.png"
}, {
    "config/ios/Resources/icons/icon-72@2x.png": 
    "platforms/ios/YourAppName/Resources/icons/icon-72@2x.png"
}, {
    "config/ios/Resources/splash/Default@2x~iphone.png": 
    "platforms/ios/YourAppName/Resources/splash/Default@2x~iphone.png"
}, {
    "config/ios/Resources/splash/Default-568h@2x~iphone.png": 
    "platforms/ios/YourAppName/Resources/splash/Default-568h@2x~iphone.png"
}, {
    "config/ios/Resources/splash/Default~iphone.png":
    "platforms/ios/YourAppName/Resources/splash/Default~iphone.png"
}, {
    "config/ios/Resources/splash/Default-Portrait~ipad.png": 
     "platforms/ios/YourAppName/Resources/splash/Default-Portrait~ipad.png"
}, {
    "config/ios/Resources/splash/Default-Portrait@2x~ipad.png": 
    "platforms/ios/YourAppName/Resources/splash/Default-Portrait@2x~ipad.png
}, ];

var fs = require('fs');
var path = require('path');

// no need to configure below
var rootdir = process.argv[2];

filestocopy.forEach(function(obj) {
    Object.keys(obj).forEach(function(key) {
        var val = obj[key];
        var srcfile = path.join(rootdir, key);
        var destfile = path.join(rootdir, val);
        //console.log("copying "+srcfile+" to "+destfile);
        var destdir = path.dirname(destfile);
        if (fs.existsSync(srcfile) && fs.existsSync(destdir)) {
            fs.createReadStream(srcfile).pipe(
               fs.createWriteStream(destfile));
        }
    });
});

Here’s another open source hook script to handle moving resource files, though I haven’t tested it. My sample hook is the 030_resource_files.js file in the download.

Conclusion

These hooks automate three common scenarios that will take place in almost any Cordova application–adding plugins to an app, switching between deployment environments, and adding custom icons and splash screens.

Write a hook anytime you have a build process that is tedious, repetitive, or required to build your app. Look carefully at any documented manual build processes. If you need to perform steps to build your app (copying icon files to various directories under platforms, for example), see if you can automate them in a hook script to make your build process quicker, easier, and more repeatable.


Dan Moore is Director of Technology for 8z Real Estate, a Colorado and California real estate brokerage, and the author of “Developing Cross Platform Mobile Applications with Cordova CLI”.

Filed in: CordovaPhoneGap 3.0 Tags:

About the Author ()

Dan Moore is Director of Technology for 8z Real Estate, a Colorado and California real estate brokerage, and the author of “Developing Cross Platform Mobile Applications with Cordova CLI”

Comments (54)

Trackback URL | Comments RSS Feed

  1. Siebmanb says:

    Hello,

    Thanks for this article.

    When using your code, I have the following error on the json : (replaced my path with XXX)
    [error] Script “XXX/.cordova/hooks/after_prepare/project.json” exited with non-zero status code. Aborting. Output: XXX/.cordova/hooks/after_prepare/project.json: line 12: syntax error: unexpected end of file

    Any idea from where it comes from ?

  2. Siebmanb says:

    I found the solution to my problem. I did not realize that the script to replace text was looking for project.json into the config directory. I put the json in the hooks directory, and CLI was trying to execute it as a script, and obviously failed to do so.

    Thanks again for that nice article. Why does nobody talks about hooks on the web ? It is awesome !!!

    • Dan Moore says:

      Hi,

      I’m glad you figured it out. I’m sorry that I neglected to mention that the project.json file needs to be placed outside of the .cordova/hooks directory. But I’m glad you figured that out. (As an aside, you’ll get the same error message if you have a hook script in the .cordova/hooks directory, but the script isn’t executable or has a syntax error.)

      I think hooks are awesome too! Please let me know of any interesting use cases you think of.

      Cheers.
      Dan

  3. Cesidio DiBenedetto says:

    Dan,

    Thanks for the mention. Your scripts are looking nice.

    I have to go back and update my scripts to add support for the other platforms, plus incorporate the recommendations you sent by email.

    Keep up the great work.

    Cesidio

  4. Adam says:

    I can’t get the this to work. I tried the one to copy the icons, but it does nothing. First I tried with phonegap cli, then I switched to cordova cli, when I noticed that the phonegap cli github says nothing about hooks. So I’m not sure if phonegap supports it, especially since phonegap doesn’t have a prepare command. But anyway, I couldn’t get it to work with corodova either. If I run cordova prepare android –verbose, I can see that the last thing it does is run the hook file, but it says “output to follow…”, followed by a blank line. I tried putting some lines of console.log in the file, but they don’t output. Do I have to edit anything in the js file besides the paths in filestocopy?

    • Dan Moore says:

      Hi Adam,

      Thanks for letting me know.

      A couple of things to troubleshoot:

      1. Did you create all the [projectroot]/config/android/res directories? (It sounds like you did.)

      2. Did you make sure the node script is executable?

      3. What directory did you put the script in?

      4. What version of cordova are you running (cordova -v)

      5. If you comment everything out, except the #!/ line and a ‘console.log(“here I am”);’ line, and run cordova prepare android, what do you see?

      Note that these scripts should work with phonegap cli as well as cordova cli, since phonegap cli just defers to cordova cli.

      Cheers.

  5. Adam says:

    1. Yes, I actually put it in [projectroot]/res/icon/android, but I updated the paths in filestocopy accordingly.

    2. Not sure what this means. I guess you mean running a chmod command? I have it set to 755 (rwxr-xr-x).

    3. [projectroot]/.cordova/hooks/after_prepare/move_icons.js

    4. I was running 3.1.0-0.1.0, but when I updated to 3.1.0-0.2.0, it started working!

    5. That helped troubleshoot. Thanks!

    Thanks so much!

  6. duc says:

    since these hooks use shebang which is unix based only. Is it possible to create similar hooks on windows? Thanks

    • Dan Moore says:

      Hi Duc,

      Yes, as long as the node scripts are valid, hooks should work. I didn’t even have to make the script executable.

      I just installed cordova 3.3.1 on my windows7 box, and created a before_platform_ls hook that was a console.log statement. (Sorry for the formatting.)

      Here’s what the hook script looked like:

      C:\path\to\project\testapp\.cordova\hooks\before_platform_ls>type hello.js
      console.log(“Hello World”);

      Here’s what the output from running ‘cordova platform ls’ looked like:

      C:\path\to\project\testapp>..\..\node_modules\cordova\bin\cordova -d platform ls
      Executing hook “”C:\path\to\project\testapp\.cordova\hooks\before_platform_ls\hello.js” “C:\path\to\project\testapp””
      Hello World

      Installed platforms:
      Available platforms: android, blackberry10, firefoxos, wp7, wp8, windows8

      • duc says:

        Hi Dan,

        Thank you for your reply, looks like mine is working as well but I’m currently having a problem with the copy icons hook. I get this error:

        [Error: Script "C:\Git\Mobile\Cordova\.cordova\hooks\after_prepare\resou
        rce_files.js" exited with non-zero status code. Aborting. Output:
        path.js:204
        throw new TypeError('Arguments to path.join must be strings');
        ^
        TypeError: Arguments to path.join must be strings
        at f (path.js:204:15)
        at Object.filter (native)
        at Object.exports.join (path.js:209:40)
        at C:\Git\Mobile\Cordova\.cordova\hooks\after_prepare\resource_files
        .js:59:28
        at Array.forEach (native)
        at C:\Git\Mobile\Cordova\.cordova\hooks\after_prepare\resource_files
        .js:57:22
        at Array.forEach (native)
        at Object. (C:\Git\Mobile\Cordova\.cordova\hooks\after_pr
        epare\resource_files.js:56:13)
        at Module._compile (module.js:456:26)
        at Object.Module._extensions..js (module.js:474:10)
        ]

        I think it is because my rootdir is null. From the tutorial code: var rootdir = process.argv[2]; I’m not quite sure where process variable comes from. Do you know how to solve this? Thanks

  7. Dan Moore says:

    Looks like there is a formatting bug on line 74 of the copy resources script. The iPad image string is not properly closed. Just an FYI to anyone reading this.

  8. Dan Moore says:

    Hi Duc,

    Have you verified that your json array is correct? The line that is causing an issue is merely iterating over that array, so I don’t know if a null root dir variable would cause that issue.

    Can you post your whole hook script?

    (I like to use jsonlint.com to validate my json data structures.)

    • duc says:

      Thank you so much for your help, I used jsonlint to validate my JSON and it is valid, here is my hook:

      #!/usr/bin/env node

      //
      // This hook copies various resource files
      // from our version control system directories
      // into the appropriate platform specific location
      //

      // configure all the files to copy.
      // Key of object is the source file,
      // value is the destination location.
      // It’s fine to put all platforms’ icons
      // and splash screen files here, even if
      // we don’t build for all platforms
      // on each developer’s box.

      var filestocopy = [{
      "config/android/res/drawable/icon.png":
      "platforms/android/res/drawable/icon.png"
      }, {
      "config/android/res/drawable-hdpi/icon.png":
      "platforms/android/res/drawable-hdpi/icon.png"
      }, {
      "config/android/res/drawable-ldpi/icon.png":
      "platforms/android/res/drawable-ldpi/icon.png"
      }, {
      "config/android/res/drawable-mdpi/icon.png":
      "platforms/android/res/drawable-mdpi/icon.png"
      }, {
      "config/android/res/drawable-xhdpi/icon.png":
      "platforms/android/res/drawable-xhdpi/icon.png"
      }, {
      "config/android/res/drawable/splash.png":
      "platforms/android/res/drawable/splash.png"
      }, {
      "config/android/res/drawable-hdpi/splash.png":
      "platforms/android/res/drawable-hdpi/splash.png"
      }, {
      "config/android/res/drawable-ldpi/splash.png":
      "platforms/android/res/drawable-ldpi/splash.png"
      }, {
      "config/android/res/drawable-mdpi/splash.png":
      "platforms/android/res/drawable-mdpi/splash.png"
      }, {
      "config/android/res/drawable-xhdpi/splash.png":
      "platforms/android/res/drawable-xhdpi/splash.png"
      }];

      var fs = require(‘fs’);
      var path = require(‘path’);

      // no need to configure below
      var rootdir = process.argv[2];

      filestocopy.forEach(function(obj) {
      Object.keys(obj).forEach(function(key) {
      var val = obj[key];
      var srcfile = path.join(rootdir, key);
      var destfile = path.join(rootdir, val);
      console.log(“copying “+srcfile+” to “+destfile);
      var destdir = path.dirname(destfile);
      if (fs.existsSync(srcfile) && fs.existsSync(destdir)) {
      fs.createReadStream(srcfile).pipe(
      fs.createWriteStream(destfile));
      }
      });
      });

      • Dan Moore says:

        Hmmm… I tried to recreate your issue. I’m on windows 7, with cordova installed locally.

        Here’s my version:
        C:\node\project\testapp>..\..\node_modules\cordova\bin\cordova -v
        3.3.1-0.1.2

        I put the hook in the before_platform_ls directory and was unable to replicate your issue.

        Here’s the output:

        C:\node\project\testapp>..\..\node_modules\cordova\bin\cordova -d platforms ls

        Executing hook “”node” “C:\node\project\testapp\.cordova\hooks\before_platform_ls\hook” “C:\node\project\testapp””
        rootdir C:\node\project\testapp
        copyingC:\node\project\testapp\config\android\res\drawable\icon.png to C:\node\project\testapp\platforms\android\res\drawable\icon.png

        Installed platforms: android
        Available platforms: blackberry10, firefoxos, wp7, wp8, windows8

        And here’s my script (‘hook’):

        #!/usr/bin/env node

        var fs = require(‘fs’);
        var path = require(‘path’);

        var filestocopy = [{
        "config/android/res/drawable/icon.png":
        "platforms/android/res/drawable/icon.png"
        }];

        // no need to configure below
        var rootdir = process.argv[2];

        filestocopy.forEach(function(obj) {
        Object.keys(obj).forEach(function(key) {
        var val = obj[key];
        var srcfile = path.join(rootdir, key);
        var destfile = path.join(rootdir, val);
        console.log(‘rootdir ‘ +rootdir);
        console.log(‘copying’ +srcfile+’ to ‘+destfile);
        var destdir = path.dirname(destfile);
        if (fs.existsSync(srcfile) && fs.existsSync(destdir)) {
        fs.createReadStream(srcfile).pipe(
        fs.createWriteStream(destfile));
        }
        });
        });

        Can you try my script and see if it works for you? What version of cordova are you using?

  9. ben funnell says:

    Great article, very useful. May I suggest an update to the replace text hook?

    Instead of hard coding the replace lines, loop through the items in project.json like so:

    for (var i in configobj[target]) {
    replace_string_in_file(fullfilename, “/\\*REP\\*/ ‘”+i+”‘ /\\*REP\\*/”, configobj[target][i]);
    }

    that way only project.json needs to be updated to add new strings to be replaced.

    • Dan Moore says:

      Ben,

      I think that’s a great shorthand, but then the default string serves two purposes. It is the key in the .json file, so it needs to document what is doing (especially since you can’t have comments in json files), and it has to be a valid value for browser tests or any other situation where you want to run right out of the www directory without doing any hook processing. So, this is the project.json file for my example that would work with your code:

      {
      “stage”:
      {
      “‘api.example.com’” : “‘qa-api.mydomain.com’”
      },
      “prod”:
      {
      “‘api.example.com’” : “‘api.mydomain.com’”
      }
      }

      As a developer, it isn’t clear what api.example.com is–and this would be more confusing if you pulled data from multiple hosts.

      But it’s a tradeoff–I definitely appreciate that one wouldn’t have to update the hook to add new replace tokens.

      • ben funnell says:

        If you use a different key, replace ‘api.example.com’ with a more descriptive key like ‘authentication.api.url’ then comments get built into the json file.

        This is the naming convention i have used in my build. This change came up when I added a new entry to project.json but had forgotten that I needed to touch the hook code and was wondering why it wasn’t working.

        • Dan Moore says:

          Hmmm… Do you ever do development against the code in project/www, like a browser or ripple? If you did, you’d have an issue with trying to reach a server with an address of ‘authentication.api.url’, no?

          • ben funnell says:

            no that’s not part of my (current) workflow, I tend to build straight to a phone for testing. I see your point though… keen to look at ripple now.

  10. ben funnell says:

    or I would if it ran in windows…

  11. Jon says:

    I took the bottom hook script which copies resources hard coded in a list and changed it to copy everything in your config file. Hope it helps someone:

    #!/usr/bin/env node

    /**
    * COPIED FROM http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/
    */

    /**
    * This hook copies all files in the config folder into the similar
    * file structure in the appropriate platform resources folder.
    *
    * eg. All folders/files from /config/ios/Resources/*
    * are moved to /platforms/ios/YourApp/Resources/*
    */

    var fs = require(‘fs’);
    var path = require(‘path’);

    var appName = “YourApp”;

    // no need to configure below
    var rootdir = process.argv[2];

    //loop through all files in the config directory
    var platformConfigs = fs.readdirSync(rootdir+”/config”);

    platformConfigs.forEach(function(platformName) {
    //ignore hidden files
    if(platformName[0] != “.”) {
    //look copy all files from this config/Resources folds into the appropriate platform
    var resourcePath = path.join(rootdir+”/config”, platformName+”/Resources”);
    fs.readdirSync(resourcePath).forEach(function(resourceType){
    //again ignore hidden files
    if(resourceType[0] != “.”) {
    var resourceTypePath = path.join(resourcePath, resourceType);
    fs.readdirSync(resourceTypePath).forEach(function(fileName){
    var srcfile = path.join(resourceTypePath, fileName);
    //determine destination location
    var destLocation = “platforms/”+platformName+”/”+appName+”/Resources/”+resourceType+”/”+fileName;
    var destfile = path.join(rootdir, destLocation);

    //console.log(“copying “+srcfile+” to “+destfile);

    if (fs.existsSync(srcfile) && fs.existsSync(destfile)) {
    fs.createReadStream(srcfile).pipe(
    fs.createWriteStream(destfile));
    }

    });
    }
    });
    }
    });

  12. Mobile Girl says:

    Great Post!

    I have a question. If I run “Cordova platform rm iOS” command through terminal, Cordova CLI is also removing iOS folder from merges directory. Phonegap’s documentation says that Platform-specific web assets (HTML, CSS and JavaScript files) are contained within appropriate subfolders in merges directory. What am I doing wrong?

  13. Rami Hadi says:

    Amazing !

    I have question , If I need to call cordova module library from node.js app which mean I won’t call it from cmd to build application for me , I need to call it inside my node.js app , how can I do it .

    everyone post how to build phonegap using CLI but no one mention how we can call these function in Node.js

    Thank you .

  14. Hey Holly,

    Thanks for the splash/icon hookup script. Also, for making clear that these aren’t going to be copied automagically with the current Cordova CLI implementation!

    Only thing I can add is that I needed to make the hookup script executable using chmod +x 010_resource_files.js and that this runs my file like a puppy on fire!

    No more custom copying each time.

    Btw: if you like Flipboard to keep up to date; I have a (Cross Platform) Mobile App Developer magazine called “EeKay’s Mobile App Developer Magazine” which has lots of mobile content for mobile devs (either native, Xamarin or Phonegap for iOS, Android and W(P)8 apps) cheers!

  15. supertramp says:

    Thanks for the amazing article !

    I have question. . How can i get command arguments in hook script?
    If i execute “cordova prepare ios” command then how can i get “ios” parameter in “before_prepare” hook ?

  16. MobileDev says:

    How do you retain the project level settings for cordova Android projects? Platforms folder removes project level setting when you run ‘cordova platform rm android’

    • Dan Moore says:

      It depends on what ‘project level settings’ you are talking about. There are a couple of different ways.

      You need to either:
      * only modify your project in ways that can be expressed in config.xml
      or
      * write an after_platform_add hook which copies your changes over (if you have a modified .java file for example)
      or
      * write a plugin which modifies any XML files (AndroidManifest.xml) to insert your needed project level config, and add that plugin to your project every time.

  17. I ran into an issue with the plug-ins hook running after_platform_add . For IOS, it seems like there might be some race conditions because each time I would create my project, I would get a random subset of the plug-ins getting initialized properly in the xcode project. Some were there, others were not. The src code was installed, but just not added to the project file.

    So, simple solution was to use the hook before_platform_add .

    Also, I needed to build the project via cordova build ios BEFORE I could open it in XCode and compile. Otherwise XCode had a ton of linker errors.

    End of the day, these scripts are amazing helpers and a big thank you for putting them out there and explaining the hook system.

  18. Anthony says:

    Sweet! Auto add splash and icons hook for the win!

  19. Anthony says:

    Hey Dan,

    I was looking for a way to add platform specific xml to the platform specific config.xml from my top-level config.xml.

    If I am getting this correct, my platform specific config.xml files are re-generated at build-time.

    Is this possible? That is, say to add a preference to the iOS config but not the Android config file.

    Thanks! Awesome blogs!

    ~Anthony

  20. Sean McAuliffe says:

    Thanks for these. The plugins install is problematic when adding more than one platform. Works the first time, then sees them as already installed. Unless there’s a way to add multiple platforms at once – not sure. Maybe installing via plugman is better? Not sure how that works but will look into and update if I get anywhere. Thanks again.

    • Dan says:

      Hi Sean,

      Hmmm… I’m not sure I ever tested this code on one machine with two different cordova supported cordova platforms.

      Hooks (as of 3.3.1-0.4.2) now send you the platform the command is being executed for: http://www.mooreds.com/wordpress/archives/1425

      But I’m not sure how that helps you. Please let us know if you come up with a solution.

    • When you install a plugin, it copies all the source code over for all platforms and sticks it in the plugins folder. So it is correct that it is already there. When you add the platform, it copies over the src files for just that platform. It also leaves behind a platform.json file in the plugins folder that keeps a record of what was installed. So you would have android.json and ios.json.

      I think my experience says that if you add plugins after you’ve created your platform, they don’t install. I usually delete the platform.json file and then run the add platform again.

  21. Dan, thanks for this excellent blog post. I documented an additional hook (for configuration settings) that might be useful to somebody: http://intown.biz/2014/05/13/build-configurations-in-cordova-using-hooks/

  22. Gordon Sun says:

    For me, the after_platform_add script in this post does not work 100% correctly.
    It will install plugins in parallel and break plugin/ios.json or plugin/android.json
    Resulting in these files missing plugins and makes the project broke.
    I had to write a thing like this in the for loop
    var newTime = new Date().getTime();
    while(true) {
    var newTime2 = new Date().getTime();
    if (newTime2 – newTime > 3000) {
    break;
    }
    }

    To break for 3 seconds between each install.
    This is not ideal. A bash script would be much better

  23. Luke Snowden says:

    Currently getting error execvp(): Permission denied any help?

  24. Luke Snowden says:

    chmod -R 777 before_prepare
    if anyone else has this issue

  25. Ivan says:

    Hi Devgirl,

    Thanks for your hooks – especially copying icon files .
    But i dislike path “”platforms/ios/YourAppName/Resources/icons/icon-72.png””
    Your AppName – why my application name is present in file system?

    How can i unlink application name from directory name?
    I didn’t found separate attribute in config.xml . and maybe there is some hook.
    Thanks

  26. Felipe says:

    Muchas gracias por los hooks, sobretodo por el “Copy Icons and Splashscreens”, me facilitó mucho las cosas.
    Gracias

Leave a Reply