3

I have and application built with PhoneGap, and I'm trying to communicate with Javascript from native code.

in my DroidGap extending class:

@Override
public void onCreate(Bundle savedInstanceState) {
    Logger.log("oncreate");
    super.onCreate(savedInstanceState);
    super.init();
    super.appView.getSettings().setJavaScriptEnabled(true);
    super.appView.getSettings().setSupportZoom(true);
    super.appView.getSettings().setBuiltInZoomControls(true);
    super.appView.getSettings().setDisplayZoomControls(false);
    jsinterface = new CommunicationInterface(this, appView);
    super.appView.addJavascriptInterface(jsinterface, "communicationinterface"); 
}

the javascriptinterface:

public class CommunicationInterface {
    private WebView mAppView;
    private DroidGap mGap;

    public CommunicationInterface(DroidGap gap, WebView view)  {
        mAppView = view;
        mGap = gap;
    }

    public String getTestString() {
        return "teststring";
    }

    public void parse(Object o) {
        Logger.log(o);
    }
}

The Javacript is located in an external file (I create an HTML file which has this line in the header: <script type="text/javascript" src="scripts.js"></script>)

Scripts.js:

function sendToInterface() {
    alert("alert");
    var map = new Object();
    (...)
    window.communicationinterface.parse(map); //communication js -> android seems to work.
}

I read in other posts that it's possible to communicate between PhoneGap and Android, but thusfar I've not had any success. I did manage to create an alert, but that was with loadUrl("javascript:alert('Alert');"), but I've also read that you shouldn't do because that's what sendJavascript() is for (and it causes leaks, reloads page, etc). I've tried to shoot a couple of Strings through the sendJavascript() method, but to no avail:

  • sendJavascript("javascript:alert('Alert');")
  • sendJavascript("javascript:sendToInterface();")
  • sendJavascript("sendToInterface();")
  • sendJavascript("window.sendToInterface();")

How to communicate from native -> PhoneGap (or what's wrong with what I already have)? Thusfar other posts and questions haven't helped me with this particular problem.

Read:

EDIT

I wrote a working project:

Java part

import org.apache.cordova.DroidGap;
import org.json.JSONException;
import org.json.JSONObject;

import android.os.Bundle;
import android.util.Log;

public class App extends DroidGap {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    super.loadUrl("file:///sdcard/ds/index.html");
    System.out.println("loading from sdcard");
    Thread t = new Thread() {
      public void run() {
        try {
          for (int i = 0; i < 3; i++) {
            sleep(2000);
            sendValue("value " + i, "another vlaue " + i);
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      };
    };
    t.start();
  }

  public void sendValue(String value1, String value2) {

    System.out.println("sendvalue in app");
    JSONObject data = new JSONObject();
    try {
      data.put("value1", value1);
      data.put("value2", value2);
    } catch (JSONException e) {
      Log.e("CommTest", e.getMessage());
    }
    String js = String.format("window.plugins.appcomm.updateValues('%s');",
        data.toString());
    this.sendJavascript(js);
  }
}

import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.util.Log;

public class AppComm extends Plugin{

  private static AppComm instance;

  public AppComm () {
    instance = this;
  }

  public static AppComm getInstance() {
    return instance;
  }

  @Override
  public PluginResult execute(String action, JSONArray args, String callbackId) {
    System.out.println("in execute from appcomm");


    return null;
  }

  public void sendValue(String value1, String value2) {
    System.out.println("sendvalue in appComm");
    JSONObject data = new JSONObject();
    try {
      data.put("value1", value1);
      data.put("value2", value2);
    } catch (JSONException e) {
      Log.e("CommTest", e.getMessage());
    }
    String js = String.format(
        "window.plugins.commtest.updateValues('%s');",
        data.toString());
    this.sendJavascript(js);
  }
}

res/xml/plugins.xml

<plugins>
    <plugin name="App" value="org.apache.cordova.App"/>
    <plugin name="Geolocation" value="org.apache.cordova.GeoBroker"/>
    <plugin name="Device" value="org.apache.cordova.Device"/>
    <plugin name="Accelerometer" value="org.apache.cordova.AccelListener"/>
    <plugin name="Compass" value="org.apache.cordova.CompassListener"/>
    <plugin name="Media" value="org.apache.cordova.AudioHandler"/>
    <plugin name="Camera" value="org.apache.cordova.CameraLauncher"/>
    <plugin name="Contacts" value="org.apache.cordova.ContactManager"/>
    <plugin name="File" value="org.apache.cordova.FileUtils"/>
    <plugin name="NetworkStatus" value="org.apache.cordova.NetworkManager"/>
    <plugin name="Notification" value="org.apache.cordova.Notification"/>
    <plugin name="Storage" value="org.apache.cordova.Storage"/>
    <plugin name="Temperature" value="org.apache.cordova.TempListener"/>
    <plugin name="FileTransfer" value="org.apache.cordova.FileTransfer"/>
    <plugin name="Capture" value="org.apache.cordova.Capture"/>
    <plugin name="Battery" value="org.apache.cordova.BatteryListener"/>
    <plugin name="SplashScreen" value="org.apache.cordova.SplashScreen"/>

    <plugin name="AppComm" value="com.example.plugin.AppComm"/>
</plugins>

cordova.xml

<?xml version="1.0" encoding="utf-8"?>
<cordova>
    <access origin=".*"/> <!-- allow local pages -->
    <log level="DEBUG"/>
    <preference name="classicRender" value="true" />
</cordova>

Index.html header

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0" />
    <title>
    </title>
    <link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.css" />
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js">
    </script>
    <script src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js">
    </script>
    <script type="text/javascript" charset="utf-8" src="cordova.js"></script>   
    <script type="text/javascript" charset="utf-8">
    var AppComm=function(){};

    AppComm.prototype.updateValues=function(a){
    var map = new Object();
    map["X1"] = "hallo";
    map["X2"] = "hi";
    cordova.exec(null, null, null);
    };

    cordova.addConstructor(function(){cordova.addPlugin("appcomm",new AppComm)});
    </script>
</head>

One of the problems was that javascript was in a separate file (I think that was one of the problems). If it isn't to much to ask, how can I properly call java back, and with what values? How to implement the execute method and how to call it (I'm really bad at JQuery)?

Community
  • 1
  • 1
stealthjong
  • 10,858
  • 13
  • 45
  • 84

2 Answers2

10

Firstly, you are using a Plugin subclass. Plugin has been deprecated and has been replaced with CordovaPlugin. If you're using an old version of PhoneGap, I would recommend that you upgrade.

Secondly, your exec call is wrong. The docs for plugin development clearly state that you have to pass 5 parameters, while you're passing 3 nulls. How do you expect that to be handled?

cordova.exec(function(winParam) {}, function(error) {}, "service",
             "action", ["firstArgument", "secondArgument", 42,
             false]);

Here, the service, action and the array of parameters determine what will happen in your Java code. The first two determine what will happen in JavaScript under certain conditions. So, while you can use null for the first two, you have to specify the last three.

I have a working example plugin that works with PhoneGap 2.3.0. See the code below:

public class ExampleJSCommunicator extends CordovaPlugin {

    public boolean execute (final String action, final JSONArray args, CallbackContext callbackContext) throws JSONException {
        PluginResult.Status status = PluginResult.Status.OK;
        String result = "";

        cordova.getActivity ().runOnUiThread (new Runnable () {
            @Override
            public void run() {
                try {
                    String displayText = "";
                    if (action.equals ("buttonClicked")) {
                        displayText = args.getString(0) + " was clicked";
                    }

                    else if (action.equals ("animationRunning")) {
                        displayText = args.getBoolean(0) ? "Animation started running" : "Animation stopped running";
                    }

                    TextView label = (TextView) cordova.getActivity().findViewById (R.id.textView);
                    label.setText (displayText + " and the Activity knows it!");
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });

        return true;
    }
}

With the code above, you have your Java-side plugin capable of handling two custom "actions" - buttonClicked and animationRunning. These actions serve my purposes, but you could name them otherwise.

Now, you still need to register your plugin, so that Cordova will know about it. This is done in the xml/config.xml file. Under plugins, you have to add the following:

<plugin name="ExampleJSCommunicator" value="com.example.phonegap.ExampleJSCommunicator"/>

Then you can pass data (or "actions") from JavaScript as follows. Note that the parameters (which.id and animationRunning are passed in an array):

cordova.exec (null, null, "ExampleJSCommunicator", "buttonClicked", [which.id]);  // my first action
cordova.exec (null, null, "ExampleJSCommunicator", "animationRunning", [animationRunning]);  // my second action

These two exec calls will trigger the execute method in the ExampleJSCommunicator class and will get handled in the respective if blocks. It doesn't matter where you call exec, as long as you declare your JavaScript code after you include the cordova.js file. My JavaScript is contained within a separate main.js file and it works just fine:

<script type="text/javascript" charset="utf-8" src="cordova.js"></script>
<script type="text/javascript" charset="utf-8" src="main.js"></script>
Shade
  • 9,936
  • 5
  • 60
  • 85
  • Sorry it took a while, but I've been busy with other things, but your example gave me the final push for getting it working both ways. It was quite confusing for me, especially the what to put where part (IE phonegap 1.7.1 has 2 xml, plugins and cordova, and you were talking about config.xml, et cetera). So, thanks. – stealthjong Apr 13 '13 at 09:03
  • Yeah, it can be a bit confusing - it took me a few hours to get it working as well. Glad I could help and thanks for the bounty :) – Shade Apr 13 '13 at 15:27
  • How do you communicate from CordovaActivity to the webview? – dazza5000 Jan 21 '19 at 05:36
  • @dazza5000 You have a property called `appView`, which is the `CordovaWebView` that's running your JS code. Check the source code here - https://github.com/apache/cordova-android/blob/master/framework/src/org/apache/cordova/CordovaActivity.java – Shade Jan 21 '19 at 09:02
  • That isnt working for me and the docs say that the sendJavascript method is deprecated. – dazza5000 Jan 21 '19 at 15:13
  • Well, that very well could be - the answer is 5 years old. However, `appView` still seems to be a property of `CordovaActivity`. Whether the rest of the details are still valid - I don't know. If you need further help, the best thing to do is to post a new question. – Shade Jan 21 '19 at 15:58
3

So far you're not actually using any of the tools that Apache Cordova gives you for making plugins and are just trying to bolt on a class with the standard Android SDK. If you are looking to add functionality, I recommend writing a plugin, because of the following reasons:

  • Apache Cordova has alternate ways of communication between the WebView and Java so that it still works even when addJavascriptInterface fails
  • You don't need to override things

I highly recommend you read this and move your custom Java code to a plugin: http://docs.phonegap.com/en/2.5.0/guide_plugin-development_android_index.md.html

However, the main bug in you example is by using a WebView in your class instead of a CordovaWebView. A WebView doesn't have a working sendJavascript method and the only simple way of sending the Javascript back is by using loadUrl.

Joe B
  • 596
  • 4
  • 11
  • Thanks, but can you elaborate a little more? See my addition. I have the most trouble with putting the pieces of example code together, especially the `execute()` method in the plugin. – stealthjong Apr 10 '13 at 09:20
  • 1
    @ChristiaandeJong the documentation is descriptive enough. Just read it. Don't try to piece together some example without having read the docs. – Shade Apr 10 '13 at 12:14
  • @Shade I've already read it (multipe times). In my code you'll see it's close to the guide. I pieced it together *using* the docs, but I can't make a fully working whole of the guide. – stealthjong Apr 10 '13 at 12:34