76

I'm trying to use the new evaluateJavascript method in Android 4.4, but all I ever get back is a null result:

webView1.evaluateJavascript("return \"test\";", new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String s) {
        Log.d("LogName", s); // Log is written, but s is always null
    }
});

How do I return a result to this method?

Update: Little bit more info:

  1. I have the INTERNET permission set
  2. I have setJavascriptEnabled(true);
  3. Tried apostrophe string: return 'test';,
  4. Tried JS object: return { test: 'this' }
  5. console.log('test'); is being executed fine.
  6. Set targetSdkVersion to 19 as per: If your app uses WebView

Devices: Both Nexus 7 and Nexus 5 (Stock)

CodingIntrigue
  • 75,930
  • 30
  • 170
  • 176
  • 1
    have you tried removing the return keyword in the javascript? So: ```webView1.evaluateJavascript("\"test\"", new ValueCallback() { /*callback code here*/ });``` – AndroidKotlinNoob Nov 17 '20 at 14:23
  • I've read through the [WebView](https://developer.android.com/guide/webapps/webview) docs and I'm trying to understand the practical use cases for the [`evaluateJavascript`](https://developer.android.com/reference/android/webkit/WebView#evaluateJavascript(java.lang.String,%20android.webkit.ValueCallback%3Cjava.lang.String%3E)) function? – AdamHurwitz Dec 07 '20 at 19:19

6 Answers6

84

There is an example of the evaluateJavascript method being used in this sample:

https://github.com/GoogleChrome/chromium-webview-samples/tree/master/jsinterface-example

Essentially if the javascript you execute in the WebView returns a value it'll be passed in the callback.

The main thing to note is that the String returned in OnReceiveValue is either a JSON Value, JSON Object or JSON Array depending on what you return.

Things to note about this is if you return a single value, you need to use setLenient(true) on a JSON reader for it to work.

     if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        // In KitKat+ you should use the evaluateJavascript method
        mWebView.evaluateJavascript(javascript, new ValueCallback<String>() {
            @TargetApi(Build.VERSION_CODES.HONEYCOMB)
            @Override
            public void onReceiveValue(String s) {
                JsonReader reader = new JsonReader(new StringReader(s));

                // Must set lenient to parse single values
                reader.setLenient(true);

                try {
                    if(reader.peek() != JsonToken.NULL) {
                        if(reader.peek() == JsonToken.STRING) {
                            String msg = reader.nextString();
                            if(msg != null) {
                                Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
                            }
                        }
                    }
                } catch (IOException e) {
                    Log.e("TAG", "MainActivity: IOException", e);
                } finally {
                    try {
                        reader.close();
                    } catch (IOException e) {
                        // NOOP
                    }
                }
            }
        });
    }

The reason you may still want to use a parser for a string response is it is converted to a JSON value which means it will be wrapped in quotes.

For example if you went:

mWebView.evaluateJavascript("(function() { return 'this'; })();", new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String s) {
        Log.d("LogName", s); // Prints: "this"
    }
});

It would print the string this, wrapped in double quotes: "this".

Other examples worth noting:

mWebView.evaluateJavascript("(function() { return null; })();", new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String s) {
        Log.d("LogName", s); // Prints the string 'null' NOT Java null
    }
});

mWebView.evaluateJavascript("(function() { })();", new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String s) {
        Log.d("LogName", s); //s is Java null
    }
});

mWebView.evaluateJavascript("(function() { return ''; })();", new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String s) {
        Log.d("LogName", s); // Prints "" (Two double quotes)
    }
});
Sam
  • 1,222
  • 1
  • 14
  • 45
Matt Gaunt
  • 9,434
  • 3
  • 36
  • 57
  • What is the use of **@TargetApi(Build.VERSION_CODES.HONEYCOMB)** inside evaluateJavascript ? – r.bhardwaj May 16 '14 at 14:27
  • Its to cover a compiler warning for the use of the JSONReader class which was introduced in Honeycomb – Matt Gaunt May 16 '14 at 14:31
  • How can we evaluateJavascript for pre KITKAT? As you checks API like- if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) – Kalu Khan Luhar Nov 07 '15 at 06:25
  • 1
    @KaluKhanLuhar you could use `webview.loadurl("javascript:(function () {document.body.background='red';});");` – lucidbrot Feb 28 '16 at 15:20
  • 1
    +1 For stating that `onReceiveValue` returns e.g. a JSON-value. Spent a lot of time trying to figure out why a call to `innerHTML` on a HTML-element returned the content in qoutes – Anigif Mar 22 '18 at 09:21
  • Before calling evaluateJavascript see this https://stackoverflow.com/a/63594924/2280067 – SJX Aug 26 '20 at 09:48
  • can someone tell me what we have to do if we have an async function inside (function(){})(); how to recieve data from that?i mean i know i async function returns an object on return but i can't figure out how to get data back from an async functioon – HiDd3N Jan 18 '21 at 14:36
  • How do I know the need to add function()? And if you need to return the value, you need to add return. No relevant instructions are found in https://developer.android.com/reference/android/webkit/WebView, thank you. – anonymous Jun 24 '21 at 10:09
17

OK, so it turns out the result here is the result of the Javascript call - as if one were entering the command into a Javascript console.

So in order to get a result, it needs to be wrapped in a function:

webView1.evaluateJavascript("(function() { return \"this\"; })();", new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String s) {
        Log.d("LogName", s); // Prints 'this'
    }
});

This will also work:

webView1.evaluateJavascript("window.variable = \"asd\";", new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String s) {
        Log.d("LogName", s); // Prints asd
    }
});

The method also handles Javascript objects:

webView1.evaluateJavascript("(function() { return { var1: \"variable1\", var2: \"variable2\" }; })();", new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String s) {
        Log.d("LogName", s); // Prints: {"var1":"variable1","var2":"variable2"}
    }
});
CodingIntrigue
  • 75,930
  • 30
  • 170
  • 176
9

AndroidJSCore is a good alternative for evaluating JavaScript that does not use a WebView.

If you want to stick with WebView and need to evaluate JavaScript on earlier versions of Android (4+), here is a little library:

https://github.com/evgenyneu/js-evaluator-for-android

jsEvaluator.evaluate("put your JavaScript code", new JsCallback() {
  @Override
  public void onResult(final String result) {
    // get result here (optional)
  }
});
Evgenii
  • 36,389
  • 27
  • 134
  • 170
  • When I use it very smilar to webview.evaluareJavaScript(), in the onPageFinished(WebView view, String url) method and I don't get the same html as I would get from evaluareJavaScript() method – Akshay Apr 19 '16 at 20:29
9

Everyone's answer is great. I just add one more point. Don't put evaluateJavascript inside the method with @JavascripInterface annotation like this way

    @JavascriptInterface  //this is the right annotation
    public void onData(){ 
            mWebView.evaluateJavascript("javascript:executeNext()",null);
    }  

Becasue it will block the JavaBridge Thread. if you want to put evaluateJavascript inside it. Do it with this way

    @JavascriptInterface
    public void onData(){
        mWebView.post(new Runnable() {
            @Override
            public void run() {
                mWebView.evaluateJavascript("javascript:executeNext()",null);
            }
        });

    }
littlebear333
  • 710
  • 2
  • 6
  • 14
  • I've read through the [WebView](https://developer.android.com/guide/webapps/webview) docs and I'm trying to understand the practical use cases for the [`evaluateJavascript`](https://developer.android.com/reference/android/webkit/WebView#evaluateJavascript(java.lang.String,%20android.webkit.ValueCallback%3Cjava.lang.String%3E)) function? – AdamHurwitz Dec 07 '20 at 19:20
  • @AdamHurwitz for example, inject some script to webview to interact with website display – Morris Mar 27 '21 at 16:52
8

To summarize the answer of @GauntFace and provide an alternative solution without using JSON parser:

If your JS function returns just a String and you're wondering about why the string is mangled in Java, it's because it's JSON-escaped.

mWebView.evaluateJavascript("(function() { return 'Earvin \"Magic\" Johnson'; })();", new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String s) {
        Log.d("LogName", s);
        // expected: s == Earvin "Magic" Johnson
        // actual:   s == "Earvin \"Magic\" Johnson"
    }
});

(note that onReceiveValue always provides a String while JS function may return a null, a Number literal etc.)

To get the string value the same as in JS, if you're 100% sure you're expecting a proper String returned, you'd need to JSON-unescape it, for example like that:

String unescaped = s.substring(1, s.length() - 1)  // remove wrapping quotes
                     .replace("\\\\", "\\")        // unescape \\ -> \
                     .replace("\\\"", "\"");       // unescape \" -> "

However, note that s might be a string "null" if JS returns proper null, so you obviously need to check that as the very first step.

if ("null".equals(s)) {
   ...
} else {
   // unescape JSON
}
jakub.g
  • 38,512
  • 12
  • 92
  • 130
2

Important hint:
Before calling evaluateJavascript you have to enable JavaScript for your WebView. Otherwise you get no result.

WebSettings settings = yourWebview.getSettings();
settings.setJavaScriptEnabled(true);
SJX
  • 1,071
  • 14
  • 15