Creating custom plugins

Although a BlackBerry web app has a wide range of functionality, that functionality is limited to the resources that the app contains and the functionality available through the existing BlackBerry and Cordova plugins. By creating a custom plugin, you can extend the functionality of your app to include the features and functions provided by core native APIs, like those available to native apps on the BlackBerry 10 OS.

You create a custom plugin in C/C++, the native development languages for BlackBerry 10 OS, and then you wrap it in JavaScript. When you build and package your web app for BlackBerry, you include the plugin source files along with other assest of your app in the package. This enables you to use JavaScript to access the plugin's functionality from within your app at any time. Using the extended function, you can pass arguments and handle return values, like you would in any native BlackBerry 10 app.

Creating a Cordova plugin for BlackBerry 10

An Apache Cordova plugin for BlackBerry 10 is composed of a Cordova JavaScript interface and native implementations of a BlackBerry specific plugin interface that the JavaScript calls into.

JavaScript is the entry point for the plugin. The JavaScript interface is front-facing and is the most important part of your Cordova plugin. In a Cordova plugin for BlackBerry 10, the native and JavaScript code communicate with each other through a framework provided by JNEXT. Every plugin must also include a plugin.xml file.

You can structure your plugin's JavaScript according to your preference. However, you must use the cordova.exec function to communicate between the Cordova JavaScript and the BlackBerry native environment. The cordova.exec function has the following signature:
exec (<successFunction>, <failFunction> , <service> , <action> , [< args >]);
Here is an example of the cordova.exec function with an explanation of its parameters:
cordova.exec(function(winParam) {}, function(error) {}, "service",
             "action", ["firstArgument", "secondArgument", 42,
             false]);
Let's take a look at each of the parameters through this example:
  • function(winParam) {} - Success function callback. Assuming your exec call completes successfully, this function is invoked (optionally with any parameters you pass back to it).
  • function(error) {} - Error function callback. If the operation does not complete successfully, this function is invoked (optionally with an error parameter).
  • "service" - The service name to call into on the native side. This is mapped to a native class in the plugin.
  • "action" - The action name to call into. This is picked up by the native class receiving the exec call, and essentially maps to a class's method.
  • [/* arguments */] - Arguments to get passed into the native environment.

Echo Plugin example

To demonstrate how to create a Cordova plugin for BlackBerry 10, let's use a very simple plugin named Echo. The Echo plugin demonstrates how to invoke native functionality from JavaScript. The plugin passes a string from JavaScript and sends it into the native environment. The native code then returns the same string (that is, echoes it) back into the callbacks inside the plugin's JavaScript.

Although this Echo tutorial is useful to help you understand the steps required to create a custom plugin, it is intentionally basic, and not intended as a template for you to follow. When you are ready to create your own custom plugin, we recommend that you start with the BlackBerry plugin template, which you can find here.

Below is the JavaScript sample code showing the use of the cordova.exec function. We attach the plugin to window, specifically to the echo function:

window.echo = function(str, callback) {
            cordova.exec(callback, function(err) {
                callback('Nothing to echo.');
            }, "Echo", "echo", [str]);
        };
Let's take a detailed look at the last three arguments to the exec function. Here, the exec function is calling the "Echo" service, requesting the "echo" action, and passing the array containing the "str" argument. The string str represents the string that is to be echoed and is the first parameter into the window.echo function. Note that the success callback passed into the exec function is simply a reference to the callback function that window.echo takes (it's the second parameter).

We do a bit more for the error callback. If the native side fires off the error callback, we simply invoke the success callback and pass into it a default string to echo back and indicate that an error has occured (using the string 'Nothing to echo.').

For example, you can use this plugin as follows:
window.echo("echome", function(echoValue) {
    alert(echoValue == "echome"); // should alert true, if there is no error
});

We can now use what we learned about the cordova.exec function and create an Echo plugin for the BlackBerry 10 platform.

Creating the JavaScript part of your plugin

The JavaScript portion of your plugin must contain a client.js and an index.js file:
  • client.js – This is considered the client side and contains the API that a Cordova application can call. The API in client.js makes calls to index.js. The API in client.js also connects callback functions to the events that fire the callbacks.
  • index.js – This is considered the server side. Cordova loads index.js and makes it accessible through the cordova.exec bridge. The client.js file makes calls to the API in the index.js file, which in turn makes calls to JNEXT to communicate with the native side.
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 });
    }
};

Create the client.js file

The client and server sides (client.js and index.js) interact through the cordova.exec() function.
In client.js you invoke the exec() function and provide the necessary arguments. In the Echo plugin, we have 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 });
    }
};

Create the index.js file

index.js interacts with the native side using JNEXT. So, you attach your constructor function to JNEXT. In our sample Echo plugin, we attach the Echo() constructor to JNEXT. Now, we have to complete the Echo() constructor and the echo() function.
  1. In your constructor, use the init() function to perform some key operations:
    1. 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")
    1. Create an object by using the acquired module and save the ID that's returned by the call.
      self.m_id = JNEXT.createObject("libecho.Echo");
    Here is the constructor from our sample Echo plugin:
    var echo;
    
    echo = new JNEXT.Echo();
    
    JNEXT.Echo = function () {
        var self = this,
            hasInstance = false;
    
        self.echo = function (text) {
            return JNEXT.invoke(self.m_id, "echo " + text);
        };
    
        self.init = function () {
            if (!JNEXT.require("libecho")) {
                return false;
            }
    
            self.m_id = JNEXT.createObject("libecho.Echo");
    
            if (self.m_id === "") {
                return false;
            }
    
            JNEXT.registerEvents(self);
        };
    
        self.m_id = "";
    
        self.getInstance = function () {
            if (!hasInstance) {
                self.init();
                hasInstance = true;
            }
            return self;
        };
    };
  2. When your application calls the echo function in client.js, that call in turn calls the echo function in index.js, where the PluginResult object sends a response (data) back to client.js. Since the args argument passed into the functions was converted by JSON.stringfy() and encoded as a URIcomponent, you must decode the data before you send it with the response:
    data = JSON.parse(decodeURIComponent(args.data));
    Now we are ready to send the data back. Here's how it turns out when you put it all together:
    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);
            }
        };

Creating the native part of your plugin

To create the native portion of your plugin, in the BlackBerry 10 Native SDK, do the following:

  1. On the File menu, click New > BlackBerry Project > Native Extension > BlackBerry WebWorks and click Next.
  2. In the Project name field, type a name for the project (for example, echo) and click Finish.
The SDK creates a native extension project and displays it in your Workbench.

System requirements

To create custom plugins to use with your web apps on BlackBerry 10 OS, you require the following:
  • BlackBerry 10 Native SDK 10.2 or later
  • Apache Cordova for BlackBerry 2.8 or later

Updating the sample code

The native extension project you create using the BlackBerry Project wizard in the IDE contains code for a sample plugin called the memory extension plugin. Replace or modify the files in the project to add functionaly according to your needs:
  • memory_js.hpp - C++ header for the JNEXT code.
  • memory_js.cpp - C++ code for JNEXT.
The native interface for the JNEXT extension can be viewed in the plugin header file located in the public folder of your project. It also contains constants and utility functions that can be used in your native code. Your 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;
    };
Therefore, your 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_
  • m_id: The m_id is an attribute that contains the JNEXT ID for this object. The ID is passed to the class as an argument to the constructor. It is needed to trigger events on the JavaScript side from native.
  • CanDelete(): The CanDelete() method is used by JNEXT to determine whether your native object can be deleted.
  • InvokeMethod(): The InvokeMethod() function is called when JavaScript invokes a method of this particular object. The only argument this function takes is a string passed from JavaScript that InvokeMethod() parses in order to determine which method of the native object should be executed.
Now we implement these functions in echo_js.cpp. For the Echo example, we implement the InvokeMethod() function as follows:
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";
        }
    }
Your native plugin must also implement the onGetObjList() and onCreateObject() callback functions:
  • extern char* onGetObjList(void);
    The onGetObjList() function returns a comma-separated list of classes supported by JNEXT. JNEXT uses this function to determine the set of classes that it can instantiate. In our Echo plugin, we have the following in echo_js.cpp:
    char* onGetObjList() {
        static char name[] = "Echo";
        return name;
    }
  • extern JSExt* onCreateObject(const string& strClassName, const string& strObjId);
    The onCreateObject() function takes two parameters. The first parameter is the name of the class requested to be created from the JavaScript side. Valid names are those that are returned in onGetObjList(). The second parameter is the unique object ID for the class. This method returns a pointer to the created plugin object. In our Echo plugin, we have the following in echo_js.cpp:
    JSExt* onCreateObject(const string& className, const string& id) {
        if (className == "Echo") {
            return new Echo(id);
        }
        return NULL;
    }

Overall architecture of the plugin

You can place the artifacts of the plugin, which include the plugin.xml file, the source files (JavaScript, C++), and the binary files (*.so), in any directory structure, as long as you specify the file locations in the plugin.xml file. Below we show a typical structure:

The structure below uses bold text for folder names and plain text for file names.

  • your_project_ folder
    • plugin.xml
    • src
      • blackberry10
        • index.js
        • native
          • device
            • *.so (binary files)
          • simulator
            • *.so (binary files)
    • www
      • client.js

Contents of the plugin.xml file

The plugin.xml file contains the namespace of the extension and other metadata. Define the namespace and specify other metadata for 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>

Last modified: 2014-07-18



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus