BlackBerry 10 Plugins
This section provides details for how to implement native plugin code on the BlackBerry 10 platform. Before reading this, see Application Plugins for an overview of the plugin's structure and its common JavaScript interface. This section continues to demonstrate the sample echo plugin that communicates from the Cordova webview to the native platform and back.
The Echo plugin basically returns whatever string the window.echo
function sends from JavaScript:
window.echo = function(str, callback) {
cordova.exec(callback, function(err) {
callback('Nothing to echo.');
}, "Echo", "echo", [str]);
};
A Cordova plugin for BlackBerry 10 contains both JavaScript and native code, which communicate with each other through a framework provided by JNEXT. Every plugin must also include a plugin.xml
file.
Creating the Native Class
To create the native portion of your plugin, open the BlackBerry 10 NDK IDE and select File → New → BlackBerry Project → Native Extension → BlackBerry 10. Enter the desired project name and location, then press Finish.
The project created by the IDE contains sample code for a memory plugin. You may replace or modify these files to implement your own functionality:
*name*_js.hpp
: C++ header for the JNEXT code.*name*_js.cpp
: C++ code for JNEXT.
The native interface for the JNEXT extension can be viewed in the plugin header file located in the project's public directory. It also features constants and utility functions available from within native code. The plugin must be derived from JSExt
, which is defined in plugin.h
. That is, you must implement the following class:
class JSExt
{
public:
virtual ~JSExt() {};
virtual string InvokeMethod( const string& strCommand ) = 0;
virtual bool CanDelete( void ) = 0;
private:
std::string m_id;
};
The extension should include the plugin.h
header file. In the Echo
example, you use JSExt
as follows in the echo_js.hpp
file:
#include "../public/plugin.h"
#include <string>
#ifndef ECHO_JS_H_
#define ECHO_JS_H_
class Echo : public JSExt
{
public:
explicit Echo(const std::string& id);
virtual ~Echo();
virtual std::string InvokeMethod(const std::string& command);
virtual bool CanDelete();
private:
std::string m_id;
};
#endif // ECHO_JS_H_
The m_id
attribute contains the JNEXT
id for the object, which is passed to the class as an argument to the constructor. It is needed for the native side to trigger events on the JavaScript side. The CanDelete
method determines whether the native object can be deleted. The InvokeMethod
function is called as a result from a request from JavaScript to invoke a method of this particular object. The only argument to this function is a string passed from JavaScript that this method parses to determine which of the native object's methods should execute. These methods are implemented in echo_js.cpp
. Here is the InvokeMethod
function for the Echo
example:
string Echo::InvokeMethod(const string& command) {
//parse command and args from string
int index = command.find_first_of(" ");
string strCommand = command.substr(0, index);
string strValue = command.substr(index + 1, command.length());
// Determine which function should be executed
if (strCommand == "echo") {
return strValue;
} else {
return "Unsupported Method";
}
}
The native plugin must also implement the following callback functions:
extern char* onGetObjList( void );
extern JSExt* onCreateObject( const string& strClassName, const string& strObjId );
The onGetObjList
function returns a comma-separated list of classes supported by JNEXT. JNEXT uses this function to determine the set of classes that JNEXT can instantiate. The Echo
plugin implements the following in echo_js.cpp
:
char* onGetObjList() {
static char name[] = "Echo";
return name;
}
The onCreateObject
function takes two parameters. The first is the name of the requested class to be created from the JavaScript side, with valid names as those returned in onGetObjList
. The second parameter is the class's unique object id. This method returns a pointer to the created plugin object. The Echo
plugin implements the following in echo_js.cpp
:
JSExt* onCreateObject(const string& className, const string& id) {
if (className == "Echo") {
return new Echo(id);
}
return NULL;
}
Creating the Plugin's JavaScript
The plugin must contain the following JavaScript files:
client.js
: This is considered the client side and contains the API available to a Cordova application. The API inclient.js
calls makes calls toindex.js
. The API inclient.js
also connects callback functions to the events that fire the callbacks.index.js
: Cordova loadsindex.js
and makes it accessible through the cordova.exec bridge. Theclient.js
file makes calls to the API in theindex.js
file, which in turn makes call to JNEXT to communicate with the native side.
The client and server side (client.js
and index.js
) interacts through the Cordova.exec
function. The client.js
needs to invoke the exec
function and provide the necessary arguments. The Echo
plugin implements the following in the client.js
file:
var service = "org.apache.cordova.blackberry.echo",
exec = cordova.require("cordova/exec");
module.exports = {
echo: function (data, success, fail) {
exec(success, fail, service, "echo", { data: data });
}
};
The index.js
component uses JNEXT to interact with the native side. Attaching a constructor function named Echo
to JNEXT allows you to perform the following key operations using the init
function:
Specify the required module exported by the native side. The name of the required module must match the name of a shared library file (
.so
file):JNEXT.require("libecho")
Create an object by using an acquired module and save the ID that's returned by the call:
self.m_id = JNEXT.createObject("libecho.Echo");
When the application calls the echo
function in client.js
, that call in turn calls the echo
function in index.js
, where the PluginResult
object sends data as a response back to client.js
. Since the args
argument passed into the functions was converted by JSON.stringfy()
and encoded as a URIcomponent
, you must call the following:
data = JSON.parse(decodeURIComponent(args.data));
You can now send the data back, as in the following:
module.exports = {
echo: function (success, fail, args, env) {
var result = new PluginResult(args, env),
data = JSON.parse(decodeURIComponent(args.data)),
response = echo.getInstance().echo(data);
result.ok(response, false);
}
};
Plugin Architecture
You can place the plugin's artifacts, including the plugin.xml
file, the JavaScript and C++ source files, and the .so
binary files within any directory structure, as long as you correctly specify the file locations in the plugin.xml
file. Here is a typical structure:
project_directory (>plugin.xml)
- www (>client.js)
- src
- blackberry10 (>index.js, native >*.cpp, *.hpp)
- device (>binary file *.so)
- simulator (>binary file *.so)
The list shows the hierarchical relationship among the top-level folders. The parenthesis shows the contents of a given directory. All directory names appear in bold text. File names are preceded by the >
sign.
The plugin.xml file
The plugin.xml
file contains the extension's namespace and other metadata. Set up the Echo
plugin as follows:
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
id="org.apache.cordova.blackberry.echo"
version="1.0.0">
<js-module src="www/client.js">
<merges target="navigator" />
</js-module>
<platform name="blackberry10">
<source-file src="src/blackberry10/index.js" />
<lib-file src="src/blackberry10/native/device/libecho.so" arch="device" />
<lib-file src="src/blackberry10/native/simulator/libecho.so" arch="simulator" />
<config-file target="www/config.xml" parent="/widget">
<feature name="org.apache.cordova.blackberry.echo" value="org.apache.cordova.blackberry.echo" />
</config-file>
</platform>
</plugin>