Extending PhoneGap with native plugins for Android
Created
23 April 2012
Page tools |
Requirements
Prerequisite knowledge
A good understanding of the fundamentals of PhoneGap and programming in Objective-C is required to make the most of this article.
User level
Intermediate
Sample files
This article examines native plugins for PhoneGap (also known as Apache Cordova) applications in Eclipse, targeting Android devices. If you are just starting out with PhoneGap or if you need to review the fundamentals of PhoneGap, readGetting started with PhoneGap in Eclipse for Android before continuing.
The terms Cordova and PhoneGap are used interchangeably within this article to refer to the same open source application platform that lets you create natively-installed mobile applications using HTML and JavaScript. The PhoneGap codebase moved to open source at Apache Software Foundation under the name Cordova. Adobe is still distributing it under the name PhoneGap. For more information, check out Brian Leroux's blog post "PhoneGap, Cordova, and what's in a name?" As Brian says in the post, "Currently, the only difference is in the name of the download package and will remain so for some time."
Not only does PhoneGap enable you to build user interfaces for natively installed mobile applications using web technologies, PhoneGap also provides a JavaScript-based API to interact with native device functionality. By default, PhoneGap provides access to the device camera, accelerometer, file system, GPS location, and media playback among other capabilities. However, PhoneGap does not expose every native API for use within your JavaScript applications. If you want PhoneGap to do more than its default feature set, you can use the PhoneGap native plugin model to extend the capabilities of the core PhoneGap API.
Native plugins in PhoneGap are not like plugins in desktop browsers; rather they provide a way for you to plug in custom code to add to what the PhoneGap application framework can do. PhoneGap native plugins enable you to create completely new, custom functionality in native code, and expose that to your PhoneGap applications via PhoneGap's native-to-JavaScript bridge. This means that you can expose any native library or framework for use within your JavaScript-based PhoneGap applications.
Before you start to write PhoneGap native plugins, it will help to understand how the PhoneGap application container exposes native operating system functionality to JavaScript-based applications.
All Cordova APIs consist of two related parts: a JavaScript-based interface that can be accessed within your applications, and the corresponding native class for performing operations in native code. Typically, the JavaScript classes and the native classes have APIs that mirrors each other, so that they are easy to follow. The JavaScript class invokes the native code using the Cordova.exec()
function. When it invokes Cordova.exec
, it can pass in a result handler function, an error handler function, and an array of parameters to be passed into native code, as well as a reference to the native class's name and native function name. Cordova will manage the JavaScript-to-native communication, and you can focus on building your application.
To learn more about PhoneGap native plugins, take a look at the core API's source code, available at the Cordova wiki. The entire PhoneGap framework is built upon the same paradigm you'll find there.
To start building your first PhoneGap native plugin, you'll need to create a new PhoneGap project following the steps outlined in the article Getting started with PhoneGap in Eclipse for Android. I named my project MyFirstPhoneGapNativePlugin.
The JavaScript class
Once you have set up your Hello Eclipse project, you're ready to create the JavaScript interface for the native plugin. Create a class with functions that mirrors the logic exposed by the native code. Under the www folder, create a JavaScript file named HelloPlugin.js that contains the simple JavaScript class shown below.
var HelloPlugin = { callNativeFunction: function (success, fail, resultType) { return cordova.exec( success, fail, "com.tricedesigns.HelloPlugin", "nativeAction", [resultType]); } };
The HelloPlugin
class has a single function named callNativeFunction
, which accepts a success callback function, an error callback function, and a resultType
string parameter. The callNativeFunction
function wraps the cordova.exec function, which invokes the actual native code. There is no additional JavaScript inside of this class, but you can add JavaScript code here if you need to.
When cordova.exec
is invoked, it expects five parameters:
- a reference to a success callback function (a function that is invoked upon a successful response from the native code layer)
- an error callback function (a function that is invoked upon an error response from the native layer)
- a string reference to the native code class (I cover this in more detail below)
- a string reference to the action that should be invoked
- This is different from iOS and other platforms. When building for Android, this is a reference to an action, not the name of the function that is invoked.
- an array of parameters to be passed into the native code
Keep in mind that code execution between the JavaScript and native code layers is not synchronous, so you'll need to use callback functions and asynchronous coding practices when developing PhoneGap native plugins.
The Native class
To create the native code layer, start by creating a new Java class that extends theorg.apache.cordova.api.Plugin
class from the core Cordova API. In Eclipse, go to File->New->Class.
Next, follow the steps in the "New Java Class" wizard, and create a class named "HelloPlugin", which extends the class "org.apache.cordova.api.Plugin".
The org.apache.cordova.api.Plugin
class is the parent class that all Cordova classes must extend. Theorg.apache.cordova.api.Plugin
class encapsulates all logic necessary for native-JavaScript communication via the PhoneGAP API.
When the cordova.exec
JavaScript function is invoked, the "execute" function on the corresponding native Plugin class is invoked. The Android WebView which is used to render HTML content in PhoneGap applications uses theWebView API to enable communication back and forth between native code and the JavaScript classes.
Once you have created your native Java class that extends org.apache.cordova.api.Plugin
, all you need to do is override the "execute" method to begin building native functionality. The parameters of this method is a string "action",JSONArray
array of parameters passed into the native code from JavaScript, and the callbackId
, which is the unique reference to the current native method invocation. Regardless of what action parameter is passed into the native class, the "execute" method is always invoked. It is up to you as the developer to evaluate the action passed into the native layer from the JavaScript layer, and respond accordingly.
Below you can see the HelloPlugin
class that extends org.apacha.cordova.api.Plugin
.
Note: Don’t forget to include the Java class com.android.Log
, otherwise you will get a compiler error in your Eclipse project.
public class HelloPlugin extends Plugin { public static final String NATIVE_ACTION_STRING="nativeAction"; public static final String SUCCESS_PARAMETER="success"; @Override public PluginResult execute(String action, JSONArray data, String callbackId) { Log.d("HelloPlugin", "Hello, this is a native function called from PhoneGap/Cordova!"); //only perform the action if it is the one that should be invoked if (NATIVE_ACTION_STRING.equals(action)) { String resultType = null; try { resultType = data.getString(0); } catch (Exception ex) { Log.d("HelloPlugin", ex.toString()); } if (resultType.equals(SUCCESS_PARAMETER)) { return new PluginResult(PluginResult.Status.OK, "Yay, Success!!!"); } else { return new PluginResult(PluginResult.Status.ERROR, "Oops, Error :("); } } return null; } }
For our HelloPlugin
class, I have extended the execute method, and the first thing that it does is write a debug message so that you can see that the native code has, in fact, been executed. Next the plugin code checks for the action "nativeAction" which you can see in the NATIVE_ACTION_STRING
above. The native code only responds if the action parameter’s value actually matches the "nativeAction" value. This technique is used to prevent misuse of the native code and while this example is basic, the technique should be used in real-world scenarios. You also need to use this technique if you have more than one action possible by a single Plugin class.
Next, after checking the native action, the resultType
string is evaluated from the JSONArray
, and an appropriatePluginResult
response is returned. If the resultType
parameter is "success", a native PluginResult
class instance is returned, with the status PluginResult.Status.OK
. For any other value, it returns a PluginResult
with the status PluginResult.Status.Error
.
When you return a result value for the execute method, you must return an instance oforg.apache.cordova.api.NativePlugin
. The status and message values returned through theNativePlugin
instance is evaluated and used to invoke the appropriate success/error callback handlers from the JavaScript code that invoked the native functionality.
Now that you have created a plugin, you can invoke it from within your PhoneGap application.
- First, you need to add a reference to the new plugin's JavaScript interface class (HelloPlugin.js). Add a new
<script>
tag inside of your index.html file:
<script type="text/javascript" charset="utf-8" src="HelloPlugin.js"></script>
- Also after the
onDeviceReady()
function, add the JavaScript for invoking the native plugin and handling the plugin results. Add JavaScript functions namedcallNativePlugin
,nativePluginResultHandler
,andnativePluginErrorHandler
as shown below:
function callNativePlugin( returnSuccess ) { HelloPlugin.callNativeFunction( nativePluginResultHandler, nativePluginErrorHandler, returnSuccess ); } function nativePluginResultHandler (result) { alert("SUCCESS: \r\n"+result ); } function nativePluginErrorHandler (error) { alert("ERROR: \r\n"+error ); }
- The
callNativePlugin
function simply calls the JavaScript interface of the native plugin class. When it invokes thecallNativeFunction
method, it passes the callback functions for success and error status received from the native code layer. ThenativePluginResultHandler
function is invoked if there is a success callback from the native layer, and thenativePluginErrorHandler
function is invoked upon an error callback from the native layer.
- Next add two JavaScript buttons as shown in the code below to invoke the plugin.
<body onload="onBodyLoad()"> <h1>Hey, it's Cordova!</h1> <button onclick="callNativePlugin('success');">Click to invoke the Native Plugin with an SUCCESS!</button> <button onclick="callNativePlugin('error');">Click to invoke the Native Plugin with an ERROR!</button> </body>
When clicked, the first button invokes the callNativeFunction
method with the parameter "success". PhoneGap then executes native code and invoke a success callback in the JavaScript layer (it invokesthenativePluginResultHandler
function).
When you click the second button, the callNativeFunction
method with is called with the parameter "error". PhoneGap executes native code and invoke an error callback in the JavaScript layer (it invokesthenativePluginErrorHandler
function).
At this point you have almost everything wired up and ready to go, but there is still one more step that you have to complete before you are able to invoke native code from JavaScript.
You have to add a mapping so that Cordova can identify your native code class. Remember the string reference that you used to identify the native class when calling cordova.exec
? You need to map that string to the actual class instance in the res/xml/plugins.xml file within your Eclipse project. The res/xml/plugins.xml file contains all configuration information for cordova native APIs available to JavaScript.
In Eclipse, open the file res/xml/plugins.xml in a text editor view. Next, you need too append a new <plugin>
XML node entry for the new native plugin that has been created. The <plugin>
XML node requires two attributes: name
andvalue
.
The "name" attribute is the value that the cordova.exec
JavaScript function uses to identify the native code. The "name" attribute is used to map to the native class identified by the "value" attribute. If you examine the plugins.xml file, you see that the name "Geolocation", which is used by the PhoneGap JavaScript API actually corresponds to the native class org.apacha.cordova.GeoBroker
.
In the new <plugin>
xml node, specify the name "com.tricedesigns.HelloPlugin" and the value "com.tricedesigns.HelloPlugin" (see Figure 3), and be sure to replace com.tricedesigns
with your own company identifier. This is the string reference that was used to identify the native class in the third parameter when calling cordova.exec
, and it maps to the com.tricedesigns.HelloPlugin
native class when invoked.
Now you are ready to launch the application and test everything out.
To launch the application right-click on the Eclipse project, then select "Run As->Android Application".
Once the application launches in the Android Emulator (or on a connected device), you see a simple interface with two buttons (see Figure 5). Click on either button to invoke the native plugin's native code, and an alert message is displayed via JavaScript upon either a success or error callback.
When the native code is invoked, you are also able to see output in the Android debug LogCat window, which reflects the output from the native plugin's Log.d
method invocations (see Figure 6).
You now know how to start creating native plugins for PhoneGap applications on Android devices. You can use this technique to access Android frameworks that are not exposed by the core PhoneGap SDK, including (but not limited to)android.net
, android.bluetooth
, and all of the Android APIs exposed via the Android Development Kit. Also, don't forget to check out the many existing PhoneGap native plugins on GitHub that have been developed by the open source community.
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available atAdobe.