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 inclient.js
, that call in turn calls theecho
function inindex.js
, where thePluginResult
object sends data as a response back toclient.js
. Since theargs
argument passed into the functions was converted byJSON.stringfy()
and encoded as aURIcomponent
, 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>