How to Write a PhoneGap 3.0 Plugin for Android

September 17, 2013By 33 Comments

Recently I posted a tutorial on how to write and use a custom plugin on Android using PhoneGap here. In this new tutorial I will show you how you can use a similar custom plugin in your PhoneGap 3.0 application.

The PhoneGap 3.0 plugin architecture has changed and there are some big difference in how it works now compared to previous versions that you need to be aware of. If you’re wondering what else you need to know about PhoneGap 3.0, also see the recent article I posted on PhoneGap 3.0 – Stuff you Should Know.

Please note that some of this is duplicated from my original tutorial but changed where necessary for PhoneGap 3.0. I wanted to keep the other tutorial available for people that are not ready to transition quite yet. The sections with the big changes for PhoneGap 3.0 are marked with an ***Updated… note.

A final working project can be found on my github account for reference here.

Overview

PhoneGap plugins allow you to extend the existing PhoneGap functionality to add your own custom features by exposing native code. The plugins are able to communicate via a bridge between the JavaScript and native code. For Android, you write your native plugin code in Java and for iOS you write it in Objective-C. The whole set of PhoneGap API’s (camera, contacts etc) were built using this same paradigm. This tutorial will focus on a plugin for Android specifically.

In a nutshell, you call a cordova.exec() method in JavaScript which directly maps to a Java execute() method in your native plugin class passing necessary parameters including success and error callback methods.
The way the bridge is implemented between Android and iOS is slightly different. In Java, objects are marshaled and available to the WebView, however in iOS it’s done via a call to a URL with a custom scheme native://xyz that gets intercepted by the native Objective-C. This is not relevant to your plugin development but worthy of noting.

Part 1: Write the Native Java Interface

Let’s begin by writing our native code that will actually add the calendar entry to our Android device so we can determine exactly which parameters will be needed on the JavaScript interface side.

  1. Open your favorite editor and define a class called Calendar.java that extends the CordovaPlugin class. This file should be created in a src folder within your custom plugin root (for example under ~/CalendarPlugin/src):
    public class Calendar extends CordovaPlugin {
    }
    
  2. Next we’ll define a static variable to define the addCalendaryEntry action to our plugin class. This is the action that we’ll pass in from the JavaScript side when we want to add a calendar entry. You can imagine that many plugins will have multiple actions that can be performed and these could all be defined in a similar fashion. For instance you could take this further later by adding other actions for editing or deleting a calendar entry in the future.

    Add the following new static variable to your Calendar class. The result looks as follows:

    public class Calendar extends CordovaPlugin {
         public static final String ACTION_ADD_CALENDAR_ENTRY = "addCalendarEntry"; 
    }
    
  3. Still in Calendar.java, add the following execute function signature. This method is inherited from the CordovaPlugin class so we’ll add the @Override annotation as well:
    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
    
    }
    
  4. Before going further we should add the necessary imports for the Java classes used so far and add a package name to Calendar.java
    Note that in addition to the CordovaPlugin class we’ll also need to import the cordova CallbackContext to use for sending back success or error messages, as well as the JSON classes to handle arguments passed in. Add the following code to the very top of your Calendar.java class.
    package com.example.myplugin;
    
    import org.apache.cordova.CallbackContext;
    import org.apache.cordova.CordovaPlugin;
    import org.json.JSONObject;
    import org.json.JSONArray;
    import org.json.JSONException;
    

    ***Updated for PhoneGap 3.0

    Note that the Java class packages for CallbackContext and CordovaPlugin were slightly changed in 3.0 so if you developed a plugin (or are using a 3rd party one that hasn’t been updated), then your imports need to look like the above example code.
  5. Within the execute() method, add code to check the action passed in and add a calendar entry via native code using the Intent and Activity classes Android provides. Intent will set up the type of activity and some other data (our custom parameters) and then get the Activity object to start a new Activity based on the Intent data. See the links on Activity and Intent classes within this paragraph for details.
    The Activity reference is made available from the Cordova interface via the getActivity() method.
    try {
        if (ACTION_ADD_CALENDAR_ENTRY.equals(action)) { 
    	         JSONObject arg_object = args.getJSONObject(0);
    	         Intent calIntent = new Intent(Intent.ACTION_EDIT)
    		.setType("vnd.android.cursor.item/event")
    		.putExtra("beginTime", arg_object.getLong("startTimeMillis"))
    		.putExtra("endTime", arg_object.getLong("endTimeMillis"))
    		.putExtra("title", arg_object.getString("title"))
    		.putExtra("description", arg_object.getString("description"))
    		.putExtra("eventLocation", arg_object.getString("eventLocation"));
    
           this.cordova.getActivity().startActivity(calIntent);
           callbackContext.success();
           return true;
        }
        callbackContext.error("Invalid action");
        return false;
    } catch(Exception e) {
        System.err.println("Exception: " + e.getMessage());
        callbackContext.error(e.getMessage());
        return false;
    } 
    
    Typically the action parameter will be checked and a separate private method called to perform the necessary action. For simplicity it was left in the execute() method.

    For more details on extras that can be used with the Android calendar intent, see this tutorial.

  6. Lastly, add the following imports for the Activity and Intent android classes needed for adding our native calendar entry to the top of Calendar.java just below the import org.json.JSONException; line added previously:
    import android.app.Activity;
    import android.content.Intent;
    
  7. Now save your file and move on to Part 2.

Part 2: Write the JavaScript Interface

Next we’ll write the JavaScript interface for our plugin. This is how way the application will communicate across the internal JavaScript-to-native bridge to execute native code.

Open your editor and create a new file called calendar.js. This file should be created within a www folder. The www and the src folders should be at the same level within your custom plugin root (~/CalendarPlugin/www).

In calendar.js, code a new calendar variable with a function named createEvent which will take the required custom parameters and callback functions needed for creating our calendar entry on the native side.

var calendar = {
    createEvent: function(title, location, notes, startDate, endDate, successCallback, errorCallback) {

    }
}

We then add the code to the createEvent function to make the required call to cordova.exec() passing the following:

  • Success Callback Function
  • Error Callback Function
  • Service Name
  • Action Name
  • Array of arguments

The calendar.js file should look like the following once the cordova.exec() code is added:

var calendar = {
    createEvent: function(title, location, notes, startDate, endDate, successCallback, errorCallback) {
        cordova.exec(
            successCallback, // success callback function
            errorCallback, // error callback function
            'Calendar', // mapped to our native Java class called "Calendar"
            'addCalendarEntry', // with this action name
            [{                  // and this array of custom arguments to create our entry
                "title": title,
                "description": notes,
                "eventLocation": location,
                "startTimeMillis": startDate.getTime(),
                "endTimeMillis": endDate.getTime()
            }]
        ); 
     }
}
The parameters will be mapped to the native Java class as follows:

  • the service name maps to the name of your native plugin class
  • the action name is passed as the first parameter of the execute() method
  • the arguments array is passed as a JSONArray in the second parameter of execute()
  • the success and error callback functions are passed as part of a CallbackContext object in the third parameter of execute()

Lastly export your calendar variable so it’s available for use with the following line of code:

module.exports = calendar;

Failing to do the export above will result in an error such as the following one below in the console (such as when running adb logcat), since the calendar object is not available.

Uncaught TypeError: Object #<Object> has no method 'createEvent' at file:///android_asset/www/js/index.js:62

Your final calendar.js file should look like the following:

var calendar =  {
    createEvent: function(title, location, notes, startDate, endDate, successCallback, errorCallback) {
        cordova.exec(
            successCallback, // success callback function
            errorCallback, // error callback function
            'Calendar', // mapped to our native Java class called "Calendar"
            'addCalendarEntry', // with this action name
            [{                  // and this array of custom arguments to create our entry
                "title": title,
                "description": notes,
                "eventLocation": location,
                "startTimeMillis": startDate.getTime(),
                "endTimeMillis": endDate.getTime()
            }]
        );
    }
}
module.exports = calendar;

Save your JavaScript file and move on to Part 3…

***Updated for PhoneGap 3.0

Part 3: Configure your Plugin to be installable with the PhoneGap CLI

To use the PhoneGap CLI to install your plugin, you must create it according to the plugin specification. Here are the steps to take:

  • Create a file named plugin.xml file within your custom plugin root (~/CalendarPlugin/) with the following:
    <?xml version="1.0" encoding="UTF-8"?>
    
    <plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
               id="org.devgirl.calendar"
          version="0.1.0">
        <name>Calendar</name>
        <description>Sample PhoneGap Calendar Plugin</description>
        <license>MIT</license>
        <keywords>phonegap,calendar</keywords>
    
    
        <js-module src="www/calendar.js" name="Calendar">
            <clobbers target="window.calendar" />
        </js-module>
    
        <!-- android -->
        <platform name="android">
            <config-file target="res/xml/config.xml" parent="/*">
                <feature name="Calendar">
                    <param name="android-package" value="org.devgirl.calendar.Calendar"/>
                </feature>
            </config-file>
    
            <source-file src="src/android/Calendar.java" target-dir="src/org/devgirl/calendar" />      
         </platform>          
    </plugin>
    
  • Check your custom plugin into a git repository. The final structure should match what is found here in my github account.

Part 4: Implement it!

Start with a basic PhoneGap starter project using the PhoneGap Command Line Interface. It’s a quick way to create a project that could later have more platforms added to it besides android and also gives you an easy way to upload your project from the command line to PhoneGap Build if desired.

Warning: you must ensure your environment path is set up to include the locations of your android tools folder and platform-tools folder (from the android-sdk download) when using any of the command line tools.

Once you have the PhoneGap CLI installed, run the following command:

$ phonegap create MyPhoneGapApp

This will create a base PhoneGap 3.0 project in a folder MyPhoneGapApp within the current directory. The MyPhoneGapApp folder will contain a sample www folder in the root but no supported platforms have been added as of yet.

Next run the following commands:

$ cd MyPhoneGapApp
$ phonegap local run android

The first ensures we’re within the root of the project directory just created. The phonegap local run android command will actually add the android platform to the project, then build and automatically deploy it to your connected Android device or emulator.

Tips:
  1. Specify whatever project name and path you’d like. If you simply use phonegap create MyPhoneGapApp without also specifying an application name or id then it will default it to HelloWorld. Specify the long version of the create command to name it yourself such as:

    $ phonegap create MyPhoneGapApp --name MyPhoneGapApp --id com.example

  2. If you have any errors when you run the phonegap command it’s likely due to your path settings. Refer to the warning above regarding adding the android-sdk tools and platform-tools folders. It’s a good idea to make sure you also have the ANDROID_HOME path variable set to the path of your android SDK location as well.

I pasted in some information from mine specifically in case it’s helpful to see when configuring yours (beware to ensure you locate your own path to your android-sdks folder):

$ echo $PATH
/Users/hollyschinsky/android-sdks/platform-tools:/Users/hollyschinsky/android-sdks/tools


$ echo $ANDROID_HOME
/Users/hollyschinsky/android-sdks

You should set these variables permanently in your profile but if you are testing to see if you missed on in a hurry you can see if that is the case quickly by adding your paths to the current terminal session with the export command such as:

$ export PATH=$PATH:/Users/hollyschinsky/android-sdks/platform-tools
$ export PATH=$PATH:/Users/hollyschinsky/android-sdks/tools
$ export ANDROID_HOME=/Users/hollyschinsky/android-sdks

These will no longer be there when you shut down your terminal window so be sure to set up a permanent solution!

You should see the following messages when your environment is configured successfully as well as have a base PhoneGap application running on your Android device or emulator at this point.:

phonegap detecting Android SDK environment...
phonegap using the local environment
phonegap adding the Android platform...
phonegap compiling Android...
phonegap successfully compiled Android app
phonegap installing app onto Android device and falling back on emulator

If you’ve never used your Android device for development, you need to go into the Settings and turn on Developer mode. This setting varies by device, but look for the Developer options. You also want to turn on USB debugging on this same menu.
You could also use the CLI commands to interface to PhoneGap Build by specifying remote instead of local. More info is provided in the PhoneGap CLI Guide.

***Updated for PhoneGap 3.0

Add the Plugin to your App

  • Run the following command to install the CalendarPlugin by pointing to your git repository or github location such as mine in the following:

    $ phonegap local plugin add https://github.com/hollyschinsky/CalendarPlugin

    Note: Ensure you see the message: [phonegap] successfully added the plugin

  • Now open the newly created project you created with the CLI command in your favorite editor.
  • Open the www/js/index.js file and add the following function directly beneath the receivedEvent function:
    ,
    addToCal: function() {
            var startDate = new Date("September 24, 2013 8:00:00");
            var endDate = new Date("September 24, 2013 18:00:00");
            var notes = "Arrive on time, don't want to miss out (from Android)";
            var title = "PhoneGap Day";
            var location = "Amsterdam";
            var notes = "Arrive on time, don't want to miss out!";
            var success = function() { alert("Success"); };
            var error = function(message) { alert("Oopsie! " + message); };
            calendar.createEvent(title, location, notes, startDate, endDate, success, error);
    }
    

    And then add the following line to call it from the onDeviceReady function:

    app.addToCal();
    
    The above step is much simpler than prior versions of PhoneGap with the help of the new plugin architecture. See my previous post about plugins to compare this section and see how much easier it is now!

    Extra Credit: Add a button to your app so you can use your new function to add calendar entries repeatedly!

Part 5: Try it!

Go back to the command line and run the following command from your project root folder:

phonegap local run android

When your application opens you should see a native calendar displayed on your device or emulator with the parameters specified in your code for the title, date, notes, location etc. Here’s a screenshot of it running on a Nexus 7:

Testing/Debugging Tips:

  1. Type phonegap help for the PhoneGap CLI command options
  2. Type adb logcat to show the android console for debugging (requires the android tools and platform tools in path)
    Tip: Use the -e or -d flags to target either emulator or device (ie: adb -d logcat).

*** Part 6: Optional – Implement multi-threading

JavaScript in the Android WebView runs on the main thread along with where the Java execute method runs, which can cause blocking issues with threading. There are options for running on a separate thread. You may choose one of two options below depending on what your native code is doing.

If your native code is interacting with the UI, then in Calendar.java you may want to specify it to run directly on the native UI thread such as follows after the action is checked:

       cordova.getActivity().runOnUiThread(new Runnable() {
            public void run() {
                // Main Code goes here
                callbackContext.success(); 
            }
        }

If you’re not interacting with the UI but if you have extensive functionality that you want to run on a separate thread, use the following after the action is checked:

cordova.getThreadPool().execute(new Runnable() {
    public void run() {
        // Main Code goes here
        callbackContext.success(); 
    }
});

*** Part 7: Optional: Final Touches…

  1. Create a readme for your plugin and explain how it’s used.
  2. Also consider contributing it to the open source repository of plugins. ** UPDATE: Also check out http://plugins.cordova.io/ for the latest plugin repository.
  3. If you’re considering submitting it for PhoneGap Build support, ensure you’ve created a plugin.xml for your new plugin.

Helpful Links

Filed in: AdobeCordovaMobile DevelopmentPhoneGapPhoneGap 3.0 Tags:

About the Author ()

Comments (33)

Trackback URL | Comments RSS Feed

  1. Marlon Harrison says:

    I’d like to build a plugin for iOS that would requires defining a Header Search Path and Library Search Path for the project…is there currently a way to do this with the plugman spec?

  2. Adam says:

    I can’t get it to work and I think part of the problem is that in your github repo you still have the .api in the imports.

    import org.apache.cordova.api.CallbackContext;
    import org.apache.cordova.api.CordovaPlugin;
    import org.json.JSONObject;
    import org.json.JSONArray;
    import org.json.JSONException;

    • Oops, just fixed that, sorry Adam. Thanks for notifying me…

      • Adam says:

        Great, now when I debug it in ADT I just get:

        Uncaught TypeError: Object # has no method ‘createEvent’ at file:///android_asset/www/js/index.js:64

        This error occurs on the line:

        calendar.createEvent(title, location, notes, startDate, endDate, success, error);

        Everything else seems to be working fine. When I installed the plugin it put everything in the correct directories. In plugins I have org.devgirl.calendar with all the files. In platforms/android/src/org/devgirl/calendar I have Calendar.java. In platforms/android/assets/www/plugins/org.devgirl.calendar/www I have calendar.js and in platforms/android/res/xml/config.xml I have the feature tag for org.devgirl.calendar.Calendar

        Any idea what’s wrong?

  3. HI Adam, I think you might be missing the last line in calendar.js where you do the:

    module.exports = calendar;

    I just checked it into github. It was in my tutorial but I had left it off in github from the < 3.0 version, sorry about that :/

  4. Alex says:

    The command line tool used on some tutorial is “phonegap”, and in others is “cordova”, I can’t understand why you don’t decide once for all the new name and start using always the same :)

    Anyway, is there a difference between the two nodejs packages?

    Which one is the “official” one?

    • Adam says:

      This explains it pretty well. As far as I understand, basically Adobe made Phonegap and then donated it to Apache and they had to rename it Cordova. But Adobe still maintains a distribution on Cordova called Phonegap. And right now, I guess there’s really not much of a difference, except Adobe offers Phonegap Build.

      http://phonegap.com/2012/03/19/phonegap-cordova-and-what%E2%80%99s-in-a-name/

      Personally I just use phonegap instead of cordova.

      • Alex says:

        I know, but I find it pretty confusing, also it’s not clear if the cordova nodejs distro is perfectly lined up with the phonegap package, newer or older (and hence, which one to install).

        I think they should use one notation once and for all.

  5. Anas Azeem says:

    Inside the calendar.js, the last statement should be module.export = calenar, not module.exports = calendar; (there should be an ‘s’ in export)

  6. Anas Azeem says:

    Inside the calendar.js, the last statement should be module.export = calendar, not module.exports = calendar; (there should be an ‘s’ in export)

  7. CpR says:

    Thanks Holly for your post.

    This is for send information from JS to Java native code right?
    How about the other way? Send information from Java native code to JS?

  8. Tntv says:

    Thanks a lot Holly. Well written document.I was able to successfully create Plugins for 3.0

  9. Naresh Ramoliya says:

    Hello,

    I am new to phonegap and developing one app in phonegap and am used SQLite Db for that but I didn’t find way to upload the db and also not found sqlite plugin on phonegap build.

    so please help me where to upload/put sqlite plugin and sqlite db when creating build using phonegap build.

  10. Hi Holly,

    Great tutorial. Your original Calendar plugin blog inspired me to create such a plugin for PhoneGap Build with iOS and Android support. It’s listed here http://build.phonegap.com/plugins and a PhoneGap 3.0 supporting version is pending for review.

    Thanks!
    Eddy

  11. Cliff Sun says:

    I just want to say thank you so much for this post and I really appreciate the thorough tutorials for phonegap 3! I was having trouble with a plugin that was old (ClipboardManagerPlugin) and wasn’t working with the newer version of phonegap. I had to remove the .api off the imports and thanks to this tutorial I figured out where to place the java file and also adding the correct xml element to the config.xml under android.

    Thanks again!

  12. Matt Lacey says:

    Another great article Holly! Thanks :)

  13. Eric W says:

    Great tutorial. Thanks. I have come back to this many times converting old Android plugins and creating new ones.

  14. Jh says:

    Thank your post..
    Some question.. How create customer plugin folder? ( ex: … plugin root (~/CalendarPlugin/) ….) That folder is created with CLI? Otherwise else?

  15. Vote 539 says:

    Nice tutorial for Android plugins! For those wanting to a tutorial for iOS, I’ve written one where you build a camera overlay. You can find it at: http://codrspace.com/vote539/writing-a-custom-camera-plugin-for-phonegap/

  16. Jeroen says:

    I’ve tried following your tutorial, but I seem to do something wrong..

    This is the plugin I’ve created:

    https://github.com/JV1988/CalendarPlugin

    It’s almost an exact copy of devgirls code. The only thing I’ve changed are the beginning of the package names from org.devgirl to com.jeroen
    But from my understanding that shouldn’t be an issue if you do it everywhere in the plugin and in the java class, right?

    Adding the plugin is working, but then I try to compile and I get a building error. I only have this problem when I try using the plugin that I created, when I add the example plugin from devgirls github, it works just fine.

    Can anyone please tell me what the problem is and explain why? It would very helpfull, Thank you!

    This is the Building Error:

    [error] An error occurred while building the android project.Error executing
    “ant debug -f “C:\..\workspace\NewTest4\platforms\android\build.xml””:

    BUILD FAILED
    C:\..\adt-bundle-windows-x86_64-20130917\sdk\tools\ant\build
    .xml:720: The following error occurred while executing this line:
    C:\..\adt-bundle-windows-x86_64-20130917\sdk\tools\ant\build
    .xml:734: Compile failed; see the compiler error output for details.

    Total time: 3 seconds

  17. Filip says:

    Dear Holly,

    great blog and great post about the plugins.

    I would appreciate very much if you or maybe some of you colleagues post more information about iOS as well.
    I started working with PhoneGAP one year ago and honestly, the 3.x version is a bit of disappointment to me, but this is another story, which is not to be discussed here.
    Anyhow, since I moved to 3.x I feel that:
    1. iOS people are having really hard time – I spent a week just trying to build and understand the changes, mostly things concerning XCODE.
    2. There is a great confusion in the community about Cordova CLI and Phonegap CLI – and I can tell you that the latest Phonegap CLI is not working well with MAC OS – it has too many problems to be a useful tool.

    At least – post a detailed information about Cordova CLI vs Phonegap CLI – how and when to use one or the other and etc.

    Again – will much appreciate more post about iOS ….

  18. Anas says:

    How can I redirect to a particular (.html) page after a notification is clicked in the status bar in Android (I want this to accomplish in iOS too, but this topic is for Android).
    Any help would be greatly appreciated.

  19. Anas says:

    I am sorry I posted it on wrong post. Now posting on the push notification post. :-\

  20. Ed Wrede says:

    Hi Holly.

    Thanks for the awesome tutorial.

    I’m using Ionic / Cordova and am trying to build my own Android plugin. I’ve followed your instructions all the way, the issue I am having is actually calling the plugin from inside my Ionic app.

    You mentioned “app.addToCal();” however in Ionic I get the error that app is undefined. I tried changing this to window.recordAudio(); but the plugin doesn’t seem to be registered under the window object, so I have no idea how to access it in my app.

    Any advice would be greatly appreciated.

    Kind regards,
    Ed

  21. Dan says:

    Hello Holly,

    First, thank you for this tutorial, you have been my primary resource when it comes to looking under the hood with Phonegap.

    I do have a question: I’ve tried various times to run things with the CLI (which I understand is the way to go) but Without fail, some of my commands just don’t run correctly and my projects don’t work correctly. Assuming i’m working in eclipse, are there any simple steps to adding the plugin to an android project manually? Thanks again for your tutorial!

    • Hi Dan! Yes there are ways to do it manually, but it’s much easier and takes less time when the CLI does it for you. Are you using the PhoneGap CLI or the Cordova CLI? Do you have any specific errors you get when using the CLI that are causing you trouble? Also, let me know what version you’re running of the CLI (type phonegap -v or cordova -v from the command line to find out). To manually add the plugin you would need to update the config.xml located within the android platform under res/xml with the feature tags for the plugin (info in that link) and then copy the source Java files into a folder structure under src/ to match the package name used in them (org.apache.cordova for instance would be in folders src/org/apache/cordova). You may have to add certain permissions manually to your manifest depending on the plugin. The CLI is much easier as you can see! Let me know if you have any errors to look at from CLI and I’ll see if I can help. Thx! Holly

Leave a Reply