Javascript calls from Android using PhoneGap

18,401

Solution 1

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>

Solution 2

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.

Share:
18,401
stealthjong
Author by

stealthjong

Software Engineer/Architect Java (experienced) Android (experienced) C# (some experience) PHP (some experience) HTML/CSS (some experience) JavaScript/jQuery (some experience) clojure (some experience) Furthermore Studying Information Technology Knowledge of Patterns and Frameworks Read books on proper/clean code Multiple (private) coding/architectural projects

Updated on June 27, 2022

Comments

  • stealthjong
    stealthjong about 2 years

    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)?