How to Write a PhoneGap 3.0 Plugin for Android

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 CordovaPluginclass. This file should be created in a src folder within your custom plugin root (for example under~/CalendarPlugin/src):
    1
    2
    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:

    1
    2
    3
    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:
    1
    2
    3
    4
    @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 cordovaCallbackContext 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.
    1
    2
    3
    4
    5
    6
    7
    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 thegetActivity() method.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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:
    1
    2
    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.

1
2
3
4
5
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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:

1
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <?xml version="1.0" encoding="UTF-8"?>
               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 yourandroid 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. TheMyPhoneGapApp 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 toHelloWorld. 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 remoteinstead 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 receivedEventfunction:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ,
    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:

    1
    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:

1
2
3
4
5
6
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:

1
2
3
4
5
6
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 outhttp://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.xmlfor your new plugin.

Helpful Links