Tutorial: How To Write a PhoneGap plugin for Android

July 17, 2013By 40 Comments

In this tutorial, you will learn how to write a custom plugin for Android and use it in your PhoneGap application. It will cover:

  • How to write a custom PhoneGap plugin to add a native Calendar entry to your Android device
  • How to implement the functionality needed for the plugin on both the JavaScript and native Java side.
  • How to create a sample base application using the new PhoneGap CLI and run it.
  • How to configure and test the sample application with the custom plugin.

A final working project can be found on my github account for reference here. Part 1 in the slides here goes along with this guide and code and may provide extra detail.

Overview

PhoneGap plugins allow you to extend the existing PhoneGap functionality to add your own custom features via 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 Obj-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 CalendarPlugin.java that extends the CordovaPlugin class:
    public class CalendarPlugin 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 CalendarPlugin class. The result looks as follows:

    public class CalendarPlugin extends CordovaPlugin {
         public static final String ACTION_ADD_CALENDAR_ENTRY = "addCalendarEntry"; 
    }
    
  3. Still in CalendarPlugin.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 classes used so far and add a package name to CalendarPlugin.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 CalendarPlugin.java class.

    package com.example.myplugin;
    
    import org.apache.cordova.api.CallbackContext;
    import org.apache.cordova.api.CordovaPlugin;
    import org.json.JSONObject;
    import org.json.JSONArray;
    import org.json.JSONException;
    
  5. Within this 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.
    Note the Activity reference is available from the cordova interface.
    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.
  6. Lastly, add the following imports for the Activity and Intent android classes needed for adding our native calendar entry to the top of CalendarPlugin.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. You could create this file in the same location as the CalendarPlugin.java file.

In calendar.js, code a new calendarPlugin 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 calendarPlugin = {
    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 calendarPlugin = {
    createEvent: function(title, location, notes, startDate, endDate, successCallback, errorCallback) {
        cordova.exec(
            successCallback, // success callback function
            errorCallback, // error callback function
            'CalendarPlugin', // mapped to our native Java class called "CalendarPlugin"
            '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()

Now you can save your JavaScript file and move on to the next part.

Part 3: Implement it!

Start with a basic PhoneGap starter project. This could be created using the PhoneGap or Cordova CLI or just using the basic command line tools and create a project for Android.

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.

I recommend using the new PhoneGap CLI. 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.

Once you install the PhoneGap CLI via npm, run the following commands to create a basic PhoneGap project, add and build for Android and automatically deploy to your connected Android device or emulator (the phonegap run android command will add the android platform config as well as build and deploy to your connected device or emulator):

phonegap create ~/MyPhoneGapApp
cd ~/MyPhoneGapApp
phonegap run android

Note: specify whatever project path you’d like, I am using my user root directory for convenience. If you have any errors, it’s likely due to your path settings. Refer to warning above regarding adding the android-sdk tools and platform-tools folders.

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.

Configure the base application to use the plugin

  • Open the newly created project in your favorite editor.
  • Add the calendar.js file to the www/js folder of your project.
  • Add the CalendarPlugin.java file created in the previous step, into the MyPhoneGapApp/platforms/android/src/com/example/myplugin folder.
    Make sure to note the package name as you will need it in the next step.
  • The config.xml file for the platform contains mappings for all the plugins used by an application. We need to add our new plugin mapping to this file. The path to this file in your project is project-root/android/res/xml/config.xml. Add the following mapping to the end of the features list in your config.xml file:
  •  <feature name="CalendarPlugin">
            <param name="android-package" value="com.example.myplugin.CalendarPlugin" />
     </feature>
    
    Warning: There’s also a config.xml file under the www folder in a default PhoneGap project. DO NOT add it to that file, you must ensure it’s added to the res/xml/config.xml path noted above specific to the platform.
  • Open www/index.html and add the reference to the calendar.js JavaScript piece of our plugin with the following script tag immediately before the index.js script tag:
    <script type="text/javascript" src="js/calendar.js"></script>
    
  • Open the www/js/index.js file and add the following function directly beneath the receivedEvent function:
    ,
    addToCal: function() {
            var startDate = new Date("July 19, 2013 8:00:00");
            var endDate = new Date("July 19, 2013 18:00:00");
            var notes = "Arrive on time, don't want to miss out (from Android)";
            var title = "PhoneGap Day";
            var location = "Portland, OR";
            var notes = "Arrive on time, don't want to miss out!";
            var success = function() { alert("Success"); };
            var error = function(message) { alert("Oopsie! " + message); };
            calendarPlugin.createEvent(title, location, notes, startDate, endDate, success, error);
    }
    

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

    app.addToCal();
    

    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 within your project root folder, run the following command:

phonegap run android

When your application opens, you should see it open the native calendar on your device 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)

*** 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 CalendarPlugin.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: CordovaPhoneGap Tags:

About the Author ()

Comments (40)

Trackback URL | Comments RSS Feed

  1. Jen Looper says:

    Thanks for this, Holly! Just in time, as I’m working on integrating Parse, Phonegap, and Android! :D

  2. Holly, this kicks ass. One small note. You may want to tell people to submit their plugin here instead of the Github repo which is going away I believe: http://plugins.cordova.io/

  3. VentureHire says:

    Hi Holly,
    It’s awesome. I have implemented it and it’s working nicely.
    Thanks for sharing such an informative tutorial. Expecting another also.

    Thanks,
    Shramik
    VentureHire

  4. Shan says:

    Hi,

    Thanks. Great Jobs, Its very useful.

  5. Zlati says:

    Is that tutorial is valid for v.3.0

  6. Mike says:

    Part 1, Step 4:

    import org.apache.cordova.api.CallbackContext;
    import org.apache.cordova.api.CordovaPlugin;

    didn’t work for me, fix:

    import org.apache.cordova.CallbackContext;
    import org.apache.cordova.CordovaPlugin;

  7. June says:

    Hi, I´m just trying your tutorial, but this folder: MyPhoneGapApp/platforms/android/src/com/example/myplugin does not exist. What did I miss ? ;)

  8. Ross Martin says:

    Thanks for the info about implementing multi-threading.

    I used your suggestions on a PhoneGap plugin I made for the Dropbox Sync API.

    Here is my blog post on the plugin – http://rossmartindev.blogspot.com/2013/08/phonegap-plugin-for-dropbox-sync-api.html

    Here is the source – https://github.com/rossmartin/phonegap-dropbox-sync

  9. Alberto Vélaz says:

    Hi Holly!

    This tutorial is fantastic… but it doesn’t work for PhoneGap 3.0 :-(
    Using your code, after changing sdk.dir I get this message:
    Oopsie! no activity found to handle intent { act=android.intent.action.EDIT typ=vnd. android.cursor.item/event(has extras) }
    :-|
    Does exist an updated guide or documentation for phonegap 3.0 and plugins?

    Thanks

    • Hi Alberto! For PG 3.0 you’ll need to modify the import statements in the .java class as it changed from 2.9:

      From this…

      import org.apache.cordova.api.CallbackContext;
      import org.apache.cordova.api.CordovaPlugin;
      

      to this…

      import org.apache.cordova.CallbackContext;
      import org.apache.cordova.CordovaPlugin;
      

      For more info on PG 3.0, see my recent post as well… http://devgirl.org/2013/09/05/phonegap-3-0-stuff-you-should-know/

      Hope that helps!
      Holly

      • Alberto Velaz says:

        Hi Holly,

        Thank you very much for your help and your fast response.
        I’ll try it and I’ll let you know the result.
        I’m really sad and tired with the current project I’m working ( Android app):
        Phonegap 3.0 + custom plugin + cordova plugins + Sencha touch 2.2 + Backbeam Android SDK
        I hope your tutorial help me a little bit.

        Thanks again

        Alberto

        • Alberto Velaz says:

          Hi Holly,

          I get the same message still.
          09-06 08:12:16.570: W/System.err(334): Exception: No Activity found to handle Intent { act=android.intent.action.EDIT typ=vnd.android.cursor.item/event (has extras) }

          I’ve changed the two lines, but it’s not working.

          Any suggestion?

          Thanks

  10. Chinta Khetiya says:

    Hello,Your tutorial is very helpful to me to crate and use own plugin in phonegap. I have follow your example in each and every step but getting some strange issue in log cat.

    ReferenceError: Can’t find variable: cordova at file:///android_asset/www/js/calendar.js:3 what’s wrong with me ?
    can you please trace it.
    Thanks and regrades.

    • Anas Azeem says:

      I am getting the same error. Did you find the answer, if yes please tell others

      I have tried add the cordova.js also in the index.html file. But it give the same error in the logcat.

  11. Do you mind if I quote a couple of your posts as long as I provide credit and sources back to
    your website? My website is in the exact same niche as yours
    and my users would definitely benefit from some of the information you provide here.
    Please let me know if this alright with you. Thank you!

  12. evilripper says:

    thanks for this useful article… why in the official docs of phonegap 2.9 there is
    [...]
    but here is it equal?

  13. Anas Azeem says:

    When I run your project forked from github, it works like a charm. But, Its all messed up in my implementation.

    It says : Error adding plugin
    exec() call to unknown plugin : CalendarPlugin

    Any ideas where I’d be missing it?

    • Anas Azeem says:

      Solved it.
      Actually I wrote the wrong code. In the config.xml while adding the node, I actually wrote , later changed it to
      Thanks by the way…. :-D

      • Anas Azeem says:

        OMG, it doesn’t support tag.
        repeating my answer--
        In the config.xml, inside the feature node--
        Earlier had
        Later corrected it to

  14. Ahmed Hassan says:

    Thank you so much this statement save my day “project-root/android/res/xml/config.xml”, i took 4 hours searching on my problem “cordova doesn’t run my plugin” because i was modify “project-root/android/assets/www/config.xml”, so thank you sooooooo much

  15. Rick says:

    Hi Holly, this is a great post, thanks so much! One question: do you know if it is possible to create an android calendar entry without displaying the native calendar? My goal is to create the calendar entry within my app without the user having to leave. It’s easy enough with iOS, but now sure if it’s achievable in Android. Thanks!

  16. Alejandro says:

    Thanks so much, after all we can find good tutorials, because the phonegap docs really sucks.

    Please continue doing this. I’m going to follow your next tips.

    Awesome

  17. CpR says:

    Hi Holly, thanks for your post and your tutorials. This is a useful post but until PhoneGap 2.9 version you have to use tag in config.xml like this official doc suggests.

    http://docs.phonegap.com/en/2.9.0/guide_plugin-development_android_index.md.html#Developing%20a%20Plugin%20on%20Android

    Is it right?
    Another issue is that in this post you receive information in Java native code from Javascript, How about send information from Java native code to JS?

    Thanks a lot!

    • CpR says:

      Sorry the xml tag was dropped…

      Hi Holly, thanks for your post and your tutorials. This is a useful post but until PhoneGap 2.9 version you have to use *plugin tag in config.xml like this official doc suggests.

  18. Geoffroy says:

    Thank you for the tutorial.

    I would like to go a bit further and create reusable plugin. Is there a way of using the cordova/phonegap CLI (cordova plugin add /path/to/my/plugin).
    Copy files around is of course a solution, but the reusability of that solution is not optimal and a “real” plugin would be better.

    Do you know where I could find that information?

    Regards

    Geoffroy

  19. TediDev says:

    Great tutorial, it is very helpful.

  20. Nitin Sarin says:

    Hello All,

    plz join me by facebook like from my comp. page for latest updates regarding phonegap or native apps.

    https://www.facebook.com/pages/Hangover/394684223963393

    http://www.hangover.co.in

    thanks

    MD

  21. Sohail says:

    when calendar intent is open then how can i get user press save or cancel button
    because Success event call every time when user click or save or cancel
    i need to differentiate

  22. Tom says:

    Hi Holly,

    I’m pretty new to phonegap so this is probably a very elementary question, but what function should I call in an onclick event within my button to get this to work?

    Thanks,
    Tom

  23. Deepanshu says:

    Hi Holly,

    I am able to register the device and able to get registration Id also.
    But when I try pushing notification using notify.js, it says null and unathourised.
    I gave the device’s IP as well as the mac’s wifi IP in the credentials of APIs.
    Kindly look into the issue.

    Thanks
    Deepanshu

  24. Lohith says:

    Hi Holly Schinsky! :)

    I followed the steps given here to create a plugin for retrieving Android accounts of any given Android device. I did it with GET ACCOUNTS of android.

    The problem is I get an error saying “Uncaught Module com.am.accountmanager.accountmanager is already defined : line 79″ in Cordova.js file. I am not sure why this is happening. I defined this package only once using cordova.define(“xxxxxxxxxxx”)….

    Could you please check on this plugin once? https://github.com/lohithkorp/AccountManagerPlugin

    Many Thanks,
    Lohith.

  25. Ajinkya says:

    Hi Holly,
    thanks a lot. It works fine. one question i wanted to ask you is that i don’t want native calendar to get open. events should get added automatically in calendar. could you please suggest me how to do it? please….

    thanks in advance…

Leave a Reply