Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript Callback function pass to Android

I have a javascript interface implemented in Java that is called by my javascript code that is loaded in the webview.

JS Inside webview:

Android.myFunction(function(data){
    console.log(data);
});

Java:

public class JavaScriptInterface {

    Context context;
    WebView webView;

    JavaScriptInterface(Context c, WebView w) {
        context = c;
        webView = w;
    }

    public void myFunction(String callback) {
        //when I log callback, it is "undefined"
         String someData = "Yay for data";
         String js =
             "javascript:(function() { "
                 + "var callback = " + callback + ";"
                 + "callback('" + someData + "');"
             + "})()";
        webView.loadUrl(js);
    }
}

The string that gets loaded by webview ends up being:

javascript:(function() {var callback = undefined; undefined();})()

I have a few ideas:

a. Build the callback in JS as a string.

b. Call the callback's toString() before passing it to Android.myFunction();

My question is, what is the best way to do this? I would love to be able to just pass objects over to Android and it magically works out. Obviously, this isn't the case. ;) What is the next best way to do it?

like image 283
Jonathan Avatar asked Aug 10 '11 23:08

Jonathan


3 Answers

I had a similar problem: From within a web app, I'd like to use a native Android confirmation dialog. That implies that I have to call back from Android into the Javascript part with the result of the confirmation dialog.

I solved this as follows:

function foo() {
    // user confirmation needed
    var dataString = <encode data into string>;
    MyClient.showConfirmationDialog('myCallBackFunction', dataString, 'A title', 'A message');
}

The code above calls the Android javascript interface (see below). The javascript provides the callback method myCallbackFunction(), the name of which is passed to Android as parameter (along with a data string, a title and a message). The callback function looks as follows:

function myCallbackFunction(dataString, result) {
    var data = <decode data from dataString>;
    if (result) {
        // user has confirmed
    } else {
        // user has denied
    }
}

On the Android side, I first activate the Javascript interface in the Activity's onCreate() method:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    WebView webView = new WebView(this);
    setContentView(webView);
    WebSettings settings = webView.getSettings();
    settings.setJavaScriptEnabled(true);
    webView.addJavascriptInterface(new MyJavascriptInterface(webView), "MyClient");
}

The implementation of MyJavascriptInterface then creates the according Android dialog and passes the result back to javascript:

    WebView webView;

    public MyJavascriptInterface(WebView w) {
         this.webView = w;
    }

    @JavascriptInterface
    public void showConfirmationDialog(final String callbackFunction, final String data, String title,
            String message) {

        Dialog.OnClickListener positiveListener = new Dialog.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                webView.loadUrl("javascript:" + callbackFunction + "('" + data + "', true)");
            }
        };
        Dialog.OnClickListener negativeListener = new Dialog.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                webView.loadUrl("javascript:" + callbackFunction + "('" + data + "', false)");
            }
        };

        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        builder.setTitle(title).setMessage(message).setPositiveButton("Ok", positiveListener)
                .setNegativeButton("Cancel", negativeListener).setCancelable(false);
        builder.create().show();
    }

Passing the callback function's name to Android allows to use several calls to confirmation dialogs, each of which is equipped with an own function do the actual action. The data string will carry all data needed to perform the action (and can even contain Json-encoded objects).

like image 137
csoltenborn Avatar answered Oct 04 '22 06:10

csoltenborn


You won't be able to pass the function in how you have it specified. You pass a function to Android.myData, but Android.myData takes a string. Instead, you probably want

var myCallback = console.log;
Android.myFunction("myCallback");

You still have a problem in that you aren't passing any data to the callback. While that's not directly related to your question, it will become an issue since you'll have the same cast to/from string issue (solvable via JSON... but it would be nice if Android handled that part for you).

Finally, you can probably shorten the javascript: string to

String js = "javascript:" + callback + "();";

But, of course, test first ;)

like image 24
David Souther Avatar answered Oct 04 '22 06:10

David Souther


The WebView allows you to directly execute some JavaScript in the window context. So you don't need to pass JavaScript via resource URL .

This is an approved way to pass data back to the html page

/**
 * This is an approved way to pass data back to the html page
 */
 webView.evaluateJavascript("alert('pass here some ...')", new ValueCallback<String>() {
       @Override
       public void onReceiveValue(String s) {

        }
    });

Details from official documentation

Asynchronously evaluates JavaScript in the context of the currently displayed page. If non-null, |resultCallback| will be invoked with any result returned from that execution. This method must be called on the UI thread and the callback will be made on the UI thread.

Compatibility note. Applications targeting N or later, JavaScript state from an empty WebView is no longer persisted across navigations like loadUrl(String). For example, global variables and functions defined before calling loadUrl(String) will not exist in the loaded page. Applications should use addJavascriptInterface(Object, String) instead to persist JavaScript objects across navigations.

Link to the official WebView documentation

like image 23
Yoraco Gonzales Avatar answered Oct 04 '22 05:10

Yoraco Gonzales