Vitaly Zinchenko
Vitaly Zinchenko

Reputation: 4911

android Cannot run Java function from Javascript

In my project I have a WebView which loads a page (HTML). I want to change all images and show a toast when a user clicks on any image.

So I'm adding javascript code which calls Java function:

// code is inside onPageFinished(..) function
JavaScriptInterface jsInterface = new JavaScriptInterface(activity);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(jsInterface, "JSInterface");

webView.evaluateJavascript(
  "var imgs = document.getElementsByTagName('img');" +
  "for (var i = 0; i < imgs.length; i++) {" +
  "imgs[i].src = 'file:///android_asset/rules_images_placeholder.png';" +
  "imgs[i].addEventListener('click', function() {" +
  "window.JSInterface.showToast(); " + // <-- isn't called: see logs
  "});" +
  "} " +
  "window.JSInterface.showToast();" // <-- is called
  , null);

JavaScriptInterface class:

public class JavaScriptInterface {
  private Activity activity;
  public JavaScriptInterface(Activity activity) {
    this.activity = activity;
  }
  @JavascriptInterface
  public void showToast() {
    Toast.makeText(activity, "Toast message", Toast.LENGTH_SHORT).show();
  }
}

showToast() should be called when

  1. page has finished loading

  2. user has clicked on image

Problem: showToast() is called only once - when page has finished loading. When the user clicks on image, showToast() isn't called, instead the following log appears:

Uncaught TypeError: Cannot read property 'showToast' of undefined", source:

Question

How to call showToast() on image click?

Upvotes: 11

Views: 2350

Answers (5)

Andriy Gordiychuk
Andriy Gordiychuk

Reputation: 6272

I am not sure why, but you can fix this problem by adding a JavaScript interface prior to loading your HTML content.

jsInterface = new JavaScriptInterface(activity);
fragmentWebView.getSettings().setJavaScriptEnabled(true);
fragmentWebView.getSettings().setDomStorageEnabled(true);
fragmentWebView.addJavascriptInterface(jsInterface, "JSInterface");

fragmentWebView.loadDataWithBaseURL("file:///android_asset/", fullHtml, "text/html", "utf-8", null);
fragmentWebView.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        view.loadUrl(
            "javascript:var imgs = document.getElementsByTagName('img');" +
            "for (var i = 0; i < imgs.length; i++) {" +
            "imgs[i].src = 'file:///android_asset/rules_images_placeholder.png';" +
            "imgs[i].addEventListener('click', function(e) {" +
            "window.JSInterface.showToast(); " +
            "});" +
            "}" +
            "console.log(window.JSInterface);" +
            "window.JSInterface.showToast(); ");
    }
});

Upvotes: 3

mico
mico

Reputation: 12748

Have you tried taking the Javascript function where you actually call Java and place it inside the html page related to the webview. Place it at headers, so it will definitively be present when click event later takes place, and won't disappear after any evaluation of function, like it probably does now in your case.

<script type="text/javascript">
    function showAndroidToast(toast) {
        JSInterface.showToast(toast);
    }
</script>

Sources I found of similar cases refer always to separate html:

[1] Toast does not work in webview

[2] Android WebView showToast onButtonClick

Upvotes: 0

Nainal
Nainal

Reputation: 1748

Remove the evaluateJavascript methoad and use loadUrl. Try the below code its working for me:-

webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient() {
        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {


        }
        @Override
        public void onPageFinished(final WebView view, String url) {
            Log.e("checking", "MYmsg");

            webView.loadUrl("javascript: var imgs = document.getElementsByTagName('img');" +
                    "for (var i = 0; i < imgs.length; i++) {" +

                    "imgs[i].addEventListener('click', function() {" +
                    "window.CallToAnAndroidFunction.showToast(); " +    
                    "});" +
                    "} " );


        }
    });
    webView.addJavascriptInterface(new WebAppInterface(Main2Activity.this),
            "CallToAnAndroidFunction");

Interface:-

public class WebAppInterface {
    Context mContext;

    /** Instantiate the interface and set the context */
    WebAppInterface(Context c) {
        mContext = c;
    }

    /** Show a toast from the web page */
    @JavascriptInterface
    public void showToast() {
        Toast.makeText(mContext, "Toast message", Toast.LENGTH_SHORT).show();
    }
}

Upvotes: 3

Alex Nikulin
Alex Nikulin

Reputation: 8679

You no need to wait for image appearance at dom level.

 string code = `
     document.addEventListener("click",(e)=> {
        if(e.target instanceof Image){
            window.JSInterface.showToast();
        }
     });`
 webView.evaluateJavascript(code, null);

At this example I used string literals => https://stackoverflow.com/a/50155171/5138198

Upvotes: 0

UdayaLakmal
UdayaLakmal

Reputation: 4213

Looks like you don't have <img> tags try adding them first and check

Android function

WebView webView= findViewById(R.id.webview);
    JavaScriptInterface jsInterface = new JavaScriptInterface(this);
    webView.getSettings().setJavaScriptEnabled(true);
    webView.addJavascriptInterface(jsInterface, "JSInterface");

    webView.evaluateJavascript(

            "document.write('<img>');" +
                    "document.write('<img>');\n"+
                    "var x = document.getElementsByTagName('IMG');" +
                    "console.log(x.length);"+
                    "for (var i = 0; i < x.length; i++) {" +
                    "x[i].src = 'https://www.imagemagick.org/Usage/images/badge_overlay.png';" +
                    "x[i].addEventListener('click', function() {" +
                    "window.JSInterface.showToast(); " +    // <-- now calling :)
                    "});" +
                    "} " +
                    "window.JSInterface.showToast();"   // <-- is called
            , null);

or you can load html content from assets first. with webView.loadUrl();

Upvotes: 0

Related Questions