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\
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
}
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"}
}
});
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)
}
});
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);
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)
}
});
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);
}
});
}