Posts Tagged android

Create javascript interface for all methods in cordova plugin

When you create a cordova plugin you need to create execute method and check for action parameter to do different task and in each task you need to create json object and call callbackContext. It would be nice if if you could just create a class with methods and each method is map to javascript method. Here is tutorial how to do this:

You need to create this ReflectService.java file:

package com.example.package;

import android.content.Context;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.PrintWriter;

import java.util.List;
import java.util.ArrayList;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaWebView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class ReflectService extends CordovaPlugin {

    protected Method getMethod(String name) {
        Class aClass = this.getClass();
        Method[] methods = aClass.getMethods();
        Method method = null;
        for (int i=0; i<methods.length; ++i) {
            if (methods[i].getName().equals(name)) {
                method = methods[i];
                break;
            }
        }
        return method;
    }

    public String[] getMethods() {
        Class aClass = this.getClass();
        Method[] methods = aClass.getDeclaredMethods();
        List<String> list = new ArrayList<String>();
        int len = methods.length;
        String[] result = new String[len];
        for (Method method : methods) {
            String name = method.getName();
            if (Modifier.isPublic(method.getModifiers()) &&
                !name.equals("initialize")) {
                list.add(name);
            }
        }
        return list.toArray(new String[list.size()]);
    }

    protected String[] jsonArrayToString(JSONArray args) {
        String[] result = {};
        try {
            int len = args.length();
            result = new String[len];
            for (int i=0; i<len; ++i) {
                result[i] = args.getString(i);
            }
        } catch (JSONException e) {}
        return result;
    }

    protected String[] getStackTrace(Exception e) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        return sw.toString().split("\n");
    }

    @Override
    public boolean execute(String action,
                           JSONArray args,
                           final CallbackContext callbackContext) throws JSONException {
        if (!action.equals("execute")) {
            final Method method = this.getMethod(action);
            if (method == null) {
                return false;
            }
            final Object[] arguments = this.jsonArrayToString(args);
            cordova.getThreadPool().execute(new Runnable() {
                public void run() {
                    JSONObject json;
                    Object result;
                    try {
                        result = method.invoke(ReflectService.this, arguments);
                        json = new JSONObject();
                        if (result instanceof Object[]) {
                            json.put("result", new JSONArray(result));
                        } else {
                            json.put("result", result);
                        }
                        json.put("error", null);
                        callbackContext.success(json);
                    } catch(JSONException e) {
                        callbackContext.success();
                    } catch(Exception e) {
                        try {
                            json = new JSONObject();
                            JSONObject error = new JSONObject();
                            error.put("error", "Exception");
                            error.put("code", 200);
                            error.put("message", e.getMessage());
                            String[] trace = ReflectService.this.getStackTrace(e);
                            error.put("trace", new JSONArray(trace));
                            json.put("error", error);
                            json.put("result", null);
                            callbackContext.success(json);
                        } catch(JSONException ee) {
                            callbackContext.success();
                        }
                    }
                }
            });
            return true;
        }
        return false;
    }
}

You probably don’t need to use threads but I’ve seen this in cordova-plugin-shell-exec.

and then you can write your Service like this:

package com.example.package;

import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaWebView;

public class Service extends ReflectService {

    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
        super.initialize(cordova, webView);
        // your init code here
    }
    public String echo(String input) {
        if (input.equals("ping")) {
            return "pong";
        } else {
            return null;
        }
    }
}

You will be able to execute each method you create from execute in ReflectService class.

Then you need to create js file to create function for each method in the class. We will use getMethods action (that will execute getMethods method) to get list of methods and create function for each name.

window.Service = function Service(callback) {
    function call(method, args, callback) {
        return cordova.exec(function(response) {
            callback(null, response);
        }, function(error) {
            callback(error);
        }, "Service", method, args || []);
    }
    var service = {};
    call("getMethods", [], function(err, response) {
        if (response.result instanceof Array) {
            response.result.forEach(function(method) {
                service[method] = function() {
                    var args = [].slice.call(arguments);
                    return function(callback) {
                        return call(method, args, function(err, response) {
                            err = err || response.error;
                            if (err) {
                                callback(err);
                            } else {
                                callback(null, response.result);
                            }
                        });
                    };
                };
            });
            callback(service);
        }
    });
};

Next you need to create plugin.xml file that look like this:

<?xml version="1.0" encoding="UTF-8"?>
<plugin id="pl.jcubic.leash.service" version="1.0.0"
        xmlns="http://apache.org/cordova/ns/plugins/1.0">
  <name>Service</name>
  <description>Apache Cordova Leash shell service plugin</description>
  <license>Apache 2.0</license>
  <keywords>cordova,exec,runtime,process,shell,command</keywords>
  <js-module name="service" src="www/service.js">
    <clobbers target="service"/>
  </js-module>
  <platform name="android">
    <config-file parent="/*" target="res/xml/config.xml">
      <feature name="Service">
        <param name="android-package" value="com.example.package.Service" />
        <param name="onload" value="true" />
      </feature>
    </config-file>
    <source-file src="src/com/example/package/Service.java"
                 target-dir="src/com/example/package/Service" />
    <source-file src="src/com/example/package/ReflectService.java"
                 target-dir="src/com/example/package/Service" />
  </platform>
</plugin>

When you add this plugin you can call it in javascript like this:

window.Service(function(service) {
    service.echo('ping')(function(err, result) {
        var pre = document.createElement('pre');
        pre.innerHTML = JSON.stringify(result, null, 4);
        document.body.appendChild(pre);
    });
});

NOTE: Instead of callbacks you can use promises.

You can find whole code in this gist.

, ,

Leave a comment

%d bloggers like this: