Overview
This is the second article in two part series about Apache Cordova. Before starting I would recommend reading through Apache Cordova – Principle, to get background on how communication happens between JS layer and Android Native layer. This post will be specific to Workflow in Apache Cordova.
Below are topics which I shall cover
- SetUp for development
- Cordova-2.1.0.js
- Important components in Cordova
- Request workflow
- Plugin Development
Assumptions
1. SetUp for development
Libraries for development can be downloaded here. Below are important components
1. cordova-2.1.0.js
2. cordova-2.1.0.jar
A good example/tutorial for developing Hello World can be found here
Code for Apache Cordova can be downloaded here . I recommend you to go ahead and download it to get better understanding
2. cordova-2.1.0.js
In part 1 of this series we saw how client interaction was facilitated by html and javascript. In case of Cordova this javascript is cordova-2.1.0.js. It should be placed in assets/www directory. It facilitates communication between client and native layer. If you look at js, there are whole bunch of methods but to get started I would look at “cordova/exec“. Below is section of function
function androidExec(success, fail, service, action, args) {
// Set default bridge modes if they have not already been set.
if (jsToNativeBridgeMode === undefined) {
androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
}
if (nativeToJsBridgeMode === undefined) {
if (callback.isAvailable()) {
androidExec.setNativeToJsBridgeMode(nativeToJsModes.HANGING_GET);
} else {
androidExec.setNativeToJsBridgeMode(nativeToJsModes.POLLING);
}
}
try {
var callbackId = service + cordova.callbackId++,
argsJson = JSON.stringify(args),
result;
if (success || fail) {
cordova.callbacks[callbackId] = {success:success, fail:fail};
}
if (jsToNativeBridgeMode == jsToNativeModes.LOCATION_CHANGE) {
window.location = 'http://cdv_exec/' + service + '#' + action + '#' + callbackId + '#' + argsJson;
} else if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT) {
// Explicit cast to string is required on Android 2.1 to convert from
// a Java string to a JS string.
result = '' + _cordovaExec.exec(service, action, callbackId, argsJson);
} else {
result = prompt(argsJson, "gap:"+JSON.stringify([service, action, callbackId, true]));
}
/////// handle result.........
}
Key Points to note
- jsToNativeBridgeMode – variable that defines various options from communication between JS and NativePlatform. Default value is PROMPT
- nativeToJsBridgeMode- variable that defines various options for communication between Native and JS.
- If application loads html from file ie. (file:// ), then callbackserver/XmlHttpRequest (HANGING_GET) is used
- If application loads html via http or any other mode POLLING is used for communication
values for both variables above are set when first time androidExec is called
- androidExec - This is the function where most of action happens
- jsToNativeBridgeMode is set to PROMPT- Line 4
- nativeToJsBridgeMode is set to HANGING_GET- Line 8
- calling prompt method with parameters which client has passed – Line 27. As discussed in part 1, this method facilitates communication between JS and Native Layer
- jsToNativeBridgeMode is set to PROMPT- Line 4
3. Important Components in PhoneGap
Below is list of important components in phonegap and their overview
- DroidGap – All application which uses Cordova framework needs to extend this class. It initializes various components and loads configuration
- CordovaWebView- This facilitates in loading of url , initializing configurations and various components needed by framework. Below are some
- CordovaWebViewClient – facilitates in rendering of html. Has callback methods like onPageStarted, onPageFinished etc.
- CordovaChromeClient – facilitates with Javascript related functionalities. Has callback methods like onJsConfirm, OnJsPrompt etc
- PluginManager – Maintains Hashmap of various plugins and facilicates calling of exec method
- CallBackServer – This server faciliates in sending asynchronous response to XMLHttpRequest.( Similar to TrimmedDownCallBackServer in part 1).
- IPlugin – Base interface for all Plugins
- Plugin – Abstract class which implements IPlugin interface. All custom plugin needs to extend this class and override execute method. In execute method you would provide functionality which interacts with native layer and gets the result. By default when you develop plugin, communication between JS and Native Layer is asynchronous, but if you want to make it synchronous , there is a method isSynch, which you need to override.
4. Request workflow
To demonstrate request workflow, lets consider an example that determines ConnectionInfo for device. This call happens during initialization of framework
Below is the javascript function which gets executed during start up. Note exec function is called.
NetworkConnection.prototype.getInfo = function (successCallback, errorCallback) {
exec(successCallback, errorCallback, "NetworkStatus", "getConnectionInfo", []);
};
Sequence diagram for request can be represented as below.
Steps Synchronous communication
- Cordova.exec is called. This function calls prompt method
- CordovaChromeClient.onJSPrompt method is intercepted
- PluginManager.exec is called
- Plugin.execute is called in same thread.
- NetworkManager is called, which calls the native library (sockMan.getActiveNetworkInfo()) and returns PluginResult
- CordovaChromeClient responds to client via JsPromptResult.onConfirm method( Pls refer to Part 1 of this series, section – Intercepting in Android)
Exceptions/Other Scenarios
- Above sequence is applicable only where workflow is synchronous
- In case of Plugins workflow is asynchronous( unless you override it)
- In asynchronous below are some changes
- NativetoJSMode is initialized – refer to section 2 above. This will be HANGING_GET( if loading url from file). It generates XMLHttpRequest
- Steps 1,2 & 3 are same as steps for synchronous communication)
- Plugin.execute called in new Thread
- PluginManager returns null.
- CordovaChromeClient responds with blank message- via JSPromptResult.onconfirm ( Pls refer to Part 1 , section - Intercepting in Android)
- Response to client request ( after calling relevant Native API) is propagated via CallbackServer
- Response is parsed via XMLHttpRequest.onreadystatechange() function and passed on to client
Essentially Corodova uses either JSPromptResult.onconfirm or Callbackserver to communicate back to client, depending on nature/scope of request
Below are some sections of code which I have included. It should help you get clarity on above sequence of events
CordovaChromeClient
///////....... do some checks
try {
array = new JSONArray(defaultValue.substring(4));
String service = array.getString(0);
String action = array.getString(1);
String callbackId = array.getString(2);
boolean async = array.getBoolean(3);
PluginResult r = this.appView.pluginManager.exec(service, action, callbackId, message, async);
result.confirm(r == null ? "" : r.getJSONString());
} catch (JSONException e) {
e.printStackTrace();
}
/////.........
PluginManager
...............
.................
final IPlugin plugin = this.getPlugin(service);
//final CordovaInterface ctx = this.ctx;
if (plugin != null) {
runAsync = async && !plugin.isSynch(action);
if (runAsync) {
// Run this on a different thread so that this one can return back to JS
Thread thread = new Thread(new Runnable() {
public void run() {
try {
// Call execute on the plugin so that it can do it's thing
PluginResult cr = plugin.execute(action, args, callbackId);
String callbackString = cr.toCallbackString(callbackId);
if (callbackString != null) {
app.sendJavascript(callbackString);
}
} catch (Exception e) {
PluginResult cr = new PluginResult(PluginResult.Status.ERROR, e.getMessage());
app.sendJavascript(cr.toErrorCallbackString(callbackId));
}
}
});
thread.start();
return null;
} else {
// Call execute on the plugin so that it can do it's thing
cr = plugin.execute(action, args, callbackId);
// If no result to be sent and keeping callback, then no need to sent back to JavaScript
if ((cr.getStatus() == PluginResult.Status.NO_RESULT.ordinal()) && cr.getKeepCallback()) {
return null;
}
}
}
......//// some other stuff
return cr;
Plugin – org.apache.cordova.NetworkManager
/////.......
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.INVALID_ACTION;
String result = "Unsupported Operation: " + action;
if (action.equals("getConnectionInfo")) {
this.connectionCallbackId = callbackId;
NetworkInfo info = sockMan.getActiveNetworkInfo();
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, this.getConnectionInfo(info));
pluginResult.setKeepCallback(true);
return pluginResult;
}
return new PluginResult(status, result);
}
public boolean isSynch(String action) {
return true;
}
5. Plugin Development
Cordova provides some built-in functionality in terms of integration with native platform. You can get list here. However if your application needs functionality not listed there, you can develop your custom plugin.
Lets consider an example scenario where you want to read all SMS from your inbox . In that case you can write plugin for same
A more detailed step-by-step guide for plugin development can be found here. Below is a quick overview
- Create a class that extends org.apache.cordova.api.Plugin
- Override execute method with functionality which you want to develop. ie for example above, it will be to access inbox and get the messages.
- Add entry for your plugin in config.xml. For name attribute, make sure you enter complete name including packages.
- Write javascript method which calls cardova.exec. Example below
- Call the javascript from your html
SMSReader Plugin section of code
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult result = null;
JSONObject messages = new JSONObject();
if (action.equals("inbox")) {
try {
messages = readSMS("inbox");
Log.d("SMSReadPlugin", "Returning " + messages.toString());
result = new PluginResult(PluginResult.Status.OK, messages);
} catch (JSONException jsonEx) {
Log.d("SMSReadPlugin", "Got JSON Exception "+ jsonEx.getMessage());
result = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
}
}
else if(action.equals("sent")){
try {
messages = readSMS("sent");
Log.d("SMSReadPlugin", "Returning " + messages.toString());
result = new PluginResult(PluginResult.Status.OK, messages);
} catch (JSONException jsonEx) {
Log.d("SMSReadPlugin", "Got JSON Exception "+ jsonEx.getMessage());
result = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
}
}
else {
result = new PluginResult(PluginResult.Status.INVALID_ACTION);
Log.d("SMSReadPlugin", "Invalid action : "+action+" passed");
}
return result;
}
Javascript – section of code
var SmsPlugin = {
callNativeFunction : function (success, fail) {
return cordova.exec(success,
fail,
"com.abstractlayers.example.SMSReader",
"inbox",
[]);
}
};
You can download the entire sourcecode for above from here
Above code is based of this example . However the example was based of Cordova-1.5 and I have updated it ( javascript and some minor changes in Java) to suit to Cordova 2.1 .
A complete list of existing plugins can be found here
That covers two part series in Apache Cordova. Hope the series was informative .
