Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can the PostMessage API be used to communicate with an Android WebView?

I usually use the HTML5 PostMessage API to communicate information from my iframed content to the parent frame. Recently I've had my content used inside an Android WebView (as far as I can tell this is the native-Android equivalent of an iframe). Is there a way for the native app to listen for PostMessage events that I send up to them?

I'm aware that addJavascriptInterface exists, I'm just hoping that there's a way to reuse my existing PostMessage code without writing something new.

like image 231
Brian Putnam Avatar asked Oct 03 '13 16:10

Brian Putnam


People also ask

How do you communicate between WebView and native Android?

2.1 To receive data from webview ,we can create an interface, which will enable webview to connect the native layer and pass data. From native layer, create a class and replicate the following. While configuring web view, we need to set JavaScript interface as above JSBridge class.

What is WebView API?

The webview API allows extensions to create fully customizable views within Visual Studio Code. For example, the built-in Markdown extension uses webviews to render Markdown previews. Webviews can also be used to build complex user interfaces beyond what VS Code's native APIs support.

Is it possible to get data from HTML forms into Android while WebView?

Yes you can, you can use javascript to get webpage content. Then use the webview jsInterface to return the content to you java code.


3 Answers

I realize this question is old but I ran into it so I figured I would answer here. In short - I am finding that postMessage does work at least for communication from a child iframe to a parent window BUT...

Turns out we really didn't like the way the iframe behaved in android's WebView so we rendered the contents of the iframe directly instead (as you suggest). This left us with two problems - first we had lots of messaging hooks from that iframe to it's parent and second we still needed to call out to android to react to these events.

Here's an example message from our code - which was sprinkled throughout the iframe:

    parent.postMessage(JSON.stringify({
        action    : 'openModal',
        source    : embedId
    }), '*');

When we're on Android what we want is to use android's support for javascript interfaces to inject an object to handle this request when running in a WebView.

On the Android side this will look something like this:

class JsObject {
   @JavascriptInterface
    public boolean postMessage(String json, String transferList) {
        return false; // here we return true if we handled the post.
    }
}

// And when initializing the webview... 
webView.addJavascriptInterface(new JsObject(), "totDevice");

Now when running inside this WebView totDevice will exist and when running in an iframe it won't. So now we can create a wrapper to check for this condition and cleanly switch between the two methods rather than calling parent.postMessage directly. Here we also added a boolean switch in our Android implementation in case you only wanted to handle some of the messages:

function postMessage(parent, json, transferlist) {
    if (!totDevice || !totDevice.postMessage(json, transferList)) {
        parent.postMessage(json, transferlist);
    }
}

Our original postMessage from above can be rewritten:

    postMessage(parent, JSON.stringify({
        action    : 'openModal',
        source    : embedId
    }), '*');

Now we have a single set of code that can run in an iframe or android WebView with no change (at least to this part of the code).

I hope that helps someone.

like image 131
darrin Avatar answered Oct 03 '22 12:10

darrin


The answers here are good answers at the time they were posted, but now that androidx.webkit is available I believe that is the recommended approach.

In particular, WebViewCompat.addWebMessageListener and WebViewCompat.postWebMessage would be the counterparts to the javascript PostMessage API.

Here is the code example copied from the documentation:

 // Web page (in JavaScript)
 myObject.onmessage = function(event) {
   // prints "Got it!" when we receive the app's response.
   console.log(event.data);
 }
 myObject.postMessage("I'm ready!");
 
 // App (in Java)
 WebMessageListener myListener = new WebMessageListener() {
   @Override
   public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
            boolean isMainFrame, JavaScriptReplyProxy replyProxy) {
     // do something about view, message, sourceOrigin and isMainFrame.
     replyProxy.postMessage("Got it!");
   }
 };
 if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
   WebViewCompat.addWebMessageListener(webView, "myObject", rules, myListener);
 }
like image 5
Maurice Lam Avatar answered Sep 30 '22 12:09

Maurice Lam


Recently we had to develop a project that needed to communicate our native Android app with an external webview integration from a third party.

The idea that this question raises in stackoverflow is very interesting, more so if you can't touch the JS code of that webview.

I describe what we did to be able to communicate the webview with the native app through the message steps via the JS PostMessage API.

Using our webview implementation. We implemented the onPageFinished method in order to inject our JS code to load the web.

override fun onPageFinished(url: String?) {
webview.loadUrl(
"javascript:(function() {" +
    "window.parent.addEventListener ('message', function(event) {" +
    " Android.receiveMessage(JSON.stringify(event.data));});" +
    "})()"
)
}

Basically what we are doing is creating a listener that sends those messages to our own JS and Android Bridge interface. Which we've previously created in the webview setup in our Android activity as we normally do with addJavascriptInterface

webview.addJavascriptInterface(JsObject(presenter), "Android”)

This way, we already have that communication bridge and all the messages sent by the postMessage will reach us in that interface that is subscribed with that listener.

class JsObject(val presenter: Presenter) {
    @JavascriptInterface
    fun receiveMessage(data: String): Boolean {
        presenter.onDataReceived(data)
        Log.d("Data from JS", data)
        return true
    }
like image 3
durbon Avatar answered Sep 30 '22 12:09

durbon