88

I want to get a return value from Javascript in Android. I can do it with the iPhone, but I can't with Android. I used loadUrl, but it returned void instead of an object. Can anybody help me?

Shashanth
  • 4,995
  • 7
  • 41
  • 51
Cuong Ta
  • 1,171
  • 1
  • 10
  • 12

10 Answers10

87

Same as Keith but shorter answer

webView.addJavascriptInterface(this, "android");
webView.loadUrl("javascript:android.onData(functionThatReturnsSomething)");

And implement the function

@JavascriptInterface
public void onData(String value) {
   //.. do something with the data
}

Don't forget to remove the onData from proguard list (if you have enabled proguard)

Kumar Bankesh
  • 296
  • 4
  • 27
Kirill Kulakov
  • 10,035
  • 9
  • 50
  • 67
  • 1
    can you elaborate on the `proguard` part? – eugene Jul 10 '14 at 13:51
  • @eugene if you don't know what it is you probably don't use it. Anyway its something to enable which makes reverse engineering your app harder – Kirill Kulakov Jul 10 '14 at 14:08
  • 1
    Thanks. I know I have proguard-project.txt in the project root but no more than that. I'm asking because your solution works but I get SIGSEGV. I am evaluating `document.getElementById("my-div").scrollTop` to get scroll position from `SwipeRefreshLayout::canChildScrollUp()`. I suspect it might be due to the call gets called too many times in short period. or `proguard` which I suspect because I just don't know what it is.. – eugene Jul 11 '14 at 01:31
  • Not necessary to use `loadUrl`. What is necessary is that you define `webView.addJavascriptInterface(this, "android");` before calling `webview.loadUrl()` method. Also, you can directly call `android.onData(result)` from anywhere inside JavaScript. Even inside an `async` function that doesn't return anything. – philoopher97 Feb 21 '23 at 17:29
64

Here's a hack on how you can accomplish it:

Add this Client to your WebView:

final class MyWebChromeClient extends WebChromeClient {
        @Override
        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
            Log.d("LogTag", message);
            result.confirm();
            return true;
        }
    }

Now in your javascript call do:

webView.loadUrl("javascript:alert(functionThatReturnsSomething)");

Now in the onJsAlert call "message" will contain the returned value.

Felix Khazin
  • 1,479
  • 2
  • 10
  • 16
  • 17
    This is an extremely valuable hack; especially when you're building an app where you don't have control over the javascript and have to make do with existing functions. Note you can get the same result without intercepting the JS alerts using the `public boolean onConsoleMessage(ConsoleMessage consoleMessage)` instead of `onJsAlert` and then in the javascript call you do `webView.loadUrl("javascript:console.log(functionThatReturnsSomething)");` - that way you leave the JS alerts alone. – Mick Byrne Jul 09 '12 at 03:46
  • The solution with `WebChromeClient.onConsoleMessage` seems to be cleaner but be aware that it does not work with some HTC devices. See [this question](http://stackoverflow.com/questions/4860021/android-problem-with-debugging-javascript-in-webview) for more info. – Piotr Jul 13 '12 at 09:29
  • 4
    Any reason using addJavascriptInterface wouldn't be preferable? – Keith Feb 11 '13 at 06:15
  • @Keith: You'd require an object into which javascript writes it results, but as you can't touch the existing javascript in this question and the existing js does not write into such object, the js interface method doesn't help here (that's how I understood it). – Ray Feb 04 '14 at 09:14
  • 1
    @RayKoopa Not necessarily true. Although you may not be able to edit the existing javascript within the target webpage, you can still invoke methods onto the object passed into `mWebView.addJavascriptInterface(YourObject mYourObject,String yourNameForObject);` by calling the function `mWebView.loadUrl("javascript:yourNameForObject.yourSetterMethod(valueToSet)")` – Chris - Jr Nov 23 '16 at 15:26
21

Use addJavascriptInterface() to add a Java object to the Javascript environment. Have your Javascript call a method on that Java object to supply its "return value".

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • how can i do that in situation like http://stackoverflow.com/questions/5521191/returning-a-value-from-javascript-function-in-android – vnshetty Apr 02 '11 at 04:59
  • This doesn't help if your js doesn't write into this object and you can't touch the js. – Ray Feb 04 '14 at 09:14
  • 7
    @PacMani: Use `loadUrl("javascript:...")` (API Level 1-18) or `evaluateJavascript()` (API Level 19+) to evaluate your own JavaScript in the context of the currently-loaded Web page. – CommonsWare Feb 04 '14 at 12:34
  • @CommonsWare: Thanks, I got that ;) however, the manager decided that js can be modified over "abusing javascript alerts" (eyeroll), so I got it going with the interface method. – Ray Feb 04 '14 at 12:49
12

Here's what I came up with today. It's thread-safe, reasonably efficient, and allows for synchronous Javascript execution from Java for an Android WebView.

Works in Android 2.2 and up. (Requires commons-lang because I need my code snippets passed to eval() as a Javascript string. You could remove this dependency by wrapping the code not in quotation marks, but in function(){})

First, add this to your Javascript file:

function evalJsForAndroid(evalJs_index, jsString) {
    var evalJs_result = "";
    try {
        evalJs_result = ""+eval(jsString);
    } catch (e) {
        console.log(e);
    }
    androidInterface.processReturnValue(evalJs_index, evalJs_result);
}

Then, add this to your Android activity:

private Handler handler = new Handler();
private final AtomicInteger evalJsIndex = new AtomicInteger(0);
private final Map<Integer, String> jsReturnValues = new HashMap<Integer, String>();
private final Object jsReturnValueLock = new Object();
private WebView webView;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    webView = (WebView) findViewById(R.id.webView);
    webView.addJavascriptInterface(new MyJavascriptInterface(this), "androidInterface");
}

public String evalJs(final String js) {
    final int index = evalJsIndex.incrementAndGet();
    handler.post(new Runnable() {
        public void run() {
            webView.loadUrl("javascript:evalJsForAndroid(" + index + ", " +
                                    "\"" + StringEscapeUtils.escapeEcmaScript(js) + "\")");
        }
    });
    return waitForJsReturnValue(index, 10000);
}

private String waitForJsReturnValue(int index, int waitMs) {
    long start = System.currentTimeMillis();

    while (true) {
        long elapsed = System.currentTimeMillis() - start;
        if (elapsed > waitMs)
            break;
        synchronized (jsReturnValueLock) {
            String value = jsReturnValues.remove(index);
            if (value != null)
                return value;

            long toWait = waitMs - (System.currentTimeMillis() - start);
            if (toWait > 0)
                try {
                    jsReturnValueLock.wait(toWait);
                } catch (InterruptedException e) {
                    break;
                }
            else
                break;
        }
    }
    Log.e("MyActivity", "Giving up; waited " + (waitMs/1000) + "sec for return value " + index);
    return "";
}

private void processJsReturnValue(int index, String value) {
    synchronized (jsReturnValueLock) {
        jsReturnValues.put(index, value);
        jsReturnValueLock.notifyAll();
    }
}

private static class MyJavascriptInterface {
    private MyActivity activity;

    public MyJavascriptInterface(MyActivity activity) {
        this.activity = activity;
    }

    // this annotation is required in Jelly Bean and later:
    @JavascriptInterface
    public void processReturnValue(int index, String value) {
        activity.processJsReturnValue(index, value);
    }
}
Keith
  • 1,123
  • 9
  • 22
  • 1
    But if I run evalJS in button.click listener, etc,. waitForJsReturnValue may cause evalJs() to hang up. So webView.loadUrl() inside handler.post() may not have chance to execute because button.click listener didn't returned. – victorwoo Jul 11 '13 at 09:18
12

On API 19+, the best way to do this is to call evaluateJavascript on your WebView:

webView.evaluateJavascript("foo.bar()", new ValueCallback<String>() {
    @Override public void onReceiveValue(String value) {
        // value is the result returned by the Javascript as JSON
    }
});

Related answer with more detail: https://stackoverflow.com/a/20377857

David
  • 2,537
  • 1
  • 22
  • 15
5

The solution that @Felix Khazin suggested works, but there is one key point missing.

The javascript call should be made after the web page in the WebView is loaded. Add this WebViewClient to the WebView, along with the WebChromeClient.

Full Example:

@Override
public void onCreate(Bundle savedInstanceState) {
    ...
    WebView webView = (WebView) findViewById(R.id.web_view);
    webView.getSettings().setJavaScriptEnabled(true);
    webView.setWebViewClient(new MyWebViewClient());
    webView.setWebChromeClient(new MyWebChromeClient());
    webView.loadUrl("http://example.com");
}

private class MyWebViewClient extends WebViewClient {
    @Override
    public void onPageFinished (WebView view, String url){
        view.loadUrl("javascript:alert(functionThatReturnsSomething())");
    }
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        return false;
    }
}

private class MyWebChromeClient extends WebChromeClient {
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
    Log.d("LogTag", message);
        result.confirm();
        return true;
    }
}
Samer
  • 1,923
  • 3
  • 34
  • 54
Mitko Katsev
  • 79
  • 1
  • 4
3

Android return value from javascript in Android WebView

As an alternative variant that uses a custom scheme to communicate Android native code <-> HTML/JS code. for example MRAID uses this technic[About]

MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            WebView.setWebContentsDebuggingEnabled(true);
        }

        final WebView webview = new CustomWebView(this);
        setContentView(webview);
        webview.loadUrl("file:///android_asset/customPage.html");

        webview.postDelayed(new Runnable() {
            @Override
            public void run() {

                //Android -> JS
                webview.loadUrl("javascript:showToast()");
            }
        }, 1000);
    }
}

CustomWebView

public class CustomWebView extends WebView {
    public CustomWebView(Context context) {
        super(context);

        setup();
    }

    @SuppressLint("SetJavaScriptEnabled")
    private void setup() {
        setWebViewClient(new AdWebViewClient());

        getSettings().setJavaScriptEnabled(true);
    }

    private class AdWebViewClient extends WebViewClient {

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {

            if (url.startsWith("customschema://")) {
                //parse uri
                Toast.makeText(CustomWebView.this.getContext(), "event was received", Toast.LENGTH_SHORT).show();

                return true;
            }
            return false;
        }

    }
}

customPage.html (located in the assets folded)

<!DOCTYPE html>
<html>
<head>
    <title>JavaScript View</title>

    <script type="text/javascript">
        <!--JS -> Android-->
        function showToast() {
            window.location = "customschema://goto/";
        }
    </script>
</head>

<body>
</body>

</html>
yoAlex5
  • 29,217
  • 8
  • 193
  • 205
0

You can do it like this:

[Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
public class MainActivity : AppCompatActivity
{
    public WebView web_view;
    public static TextView textView;

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        Xamarin.Essentials.Platform.Init(this, savedInstanceState);

        Window.AddFlags(WindowManagerFlags.Fullscreen);
        Window.ClearFlags(WindowManagerFlags.ForceNotFullscreen);

        // Set our view from the "main" layout resource
        SetContentView (Resource.Layout.main);

        web_view = FindViewById<WebView>(Resource.Id.webView);
        textView = FindViewById<TextView>(Resource.Id.textView);

        web_view.Settings.JavaScriptEnabled = true;
        web_view.SetWebViewClient(new SMOSWebViewClient());
        web_view.LoadUrl("https://stns.egyptair.com");
    }

    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
    {
        Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

public class SMOSWebViewClient : WebViewClient
{
    public override bool ShouldOverrideUrlLoading(WebView view, IWebResourceRequest request)
    {
        view.LoadUrl(request.Url.ToString());
        return false;
    }

    public override void OnPageFinished(WebView view, string url)
    {
        view.EvaluateJavascript("document.getElementsByClassName('notf')[0].innerHTML;", new JavascriptResult());
    }
}

public class JavascriptResult : Java.Lang.Object, IValueCallback
{
    public string Result;

    public void OnReceiveValue(Java.Lang.Object result)
    {
        string json = ((Java.Lang.String)result).ToString();
        Result = json;
        MainActivity.textView.Text = Result.Replace("\"", string.Empty);
    }
}
Sherif Riad
  • 157
  • 12
0

If you want to get the value in a synchronous way, you have to know that the webView.evaluateJavascript() runs asynchronously but you can use a simple semaphore to wait for the execution.

Steps:

  1. Create a javascript interface (basically a class with a method that is going to be called from the javascript we want to evaluate.

     public class MyInterface 
     {
         private String value;
         private Semaphore semaphore;
    
         public MyInterface(Semaphore semaphore) {
             this.semaphore = semaphore;
         }
    
         public String getValue() {
             return value;
         }
    
         @JavascriptInterface
         public void setValue(String value) {
             this.value = value;
             //important release the semaphore after the execution
             semaphore.release();
         }
     }
    
  2. Add the javascript interface to the Android web view

     Semaphore mySemaphore = new Semaphore(0);
     MyInterface myJsInterface = new MyInterface(mySemaphore);
     webView.addJavascriptInterface(myJsInterface, "jsInterface");
    
  3. Execute the javascript and await the execution

     webView.evaluateJavascript("var myValue='this works';jsInterface.setValue(myValue);", null);
     //await the execution
     mySemaphore.acquire();
     //the interface now have the value after the execution
     myJsInterface.getValue();
    
0

To get data from a WebView in Android, you can use the evaluateJavascript() method to execute JavaScript code that retrieves the data from the web page. Here's an example:

WebView webView = findViewById(R.id.webview); webView.getSettings().setJavaScriptEnabled(true); webView.loadUrl("https://example.com");

String js = "(function() { " +
        "return document.getElementById('my_element').innerHTML;" +
        "})()";

webView.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        view.evaluateJavascript(js, new ValueCallback<String>() {
            @Override
            public void onReceiveValue(String value) {
                // 'value' contains the result of the JavaScript code
                Log.d("WebView", "Data: " + value);
            }
        });
    }
});

In this example, the JavaScript code retrieves the inner HTML of an element with the ID my_element. The evaluateJavascript() method executes the code and passes the result to a ValueCallback that logs the data to the console. You can modify the JavaScript code to retrieve different types of data, such as form input values or attributes of HTML elements. Just make sure to return the data as a string, and parse it in the onReceiveValue() method if necessary.

Mori
  • 2,653
  • 18
  • 24