Communication with WebView in Android

WebView is a special component that behaves as a built-in browser inside Android application. WebView enables Android application to render HTML and CSS code, execute JavaScript code and browse internet websites. This component is not utilized very often, but, in many cases, using WebView is the best approach to accommodate special requirements.

In this post, I’ll explain how to establish communication between the “native” code of an Android application and the internals of a WebView, which is surprisingly challenging task.

HTML, CSS and JavaScript in Android WebView

Depending on your requirements, you can fetch the contents of the WebView from an external URL using webView.loadUrl("<url>") method, or you can bind the code directly to the WebView (e.g. after loading it from a file) by calling webView.loadDataWithBaseURL("", html, "text/html", "UTF-8", null). The approaches you’ll see below will work in both of these cases.

For the purpose of this tutorial, I’m asking you to imagine that you have a WebView that executes the following code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        function getToastMessage() {
            return document.getElementById('toastMessage').value;
        }

        function concatenateStrings(arg1, arg2) {
            return arg1 + arg2;
        }

        function showToast() {
            MyJavascriptInterface.showToast(getToastMessage());
        }
    </script>
</head>
<body>

<div style="max-width:960px; margin: auto; text-align:center;">
    <div style="margin-top: 50px;">
        <button id="changeColor" type="button" onclick="this.style.backgroundColor='red'">Change color</button>
    </div>

    <div style="margin-top: 50px;">
        <input id="toastMessage" type="text" placeholder="Toast message"/>
    </div>

    <div style="margin-top: 50px;">
        <button id="showToast" type="button" onclick="showToast()">Show toast</button>
    </div>
</div>

</body>
</html>

Don’t worry if this code doesn’t make much sense. I designed it just to demonstrate various aspects of communication with WebView in the following sections of this post.

Enable WebView’s JavaScript Support

According to the above HTML, the topmost button should change its own color when you click on it. However, in practice, if you just push this code into a WebView, chances are that it’ll do nothing. The reason for this is that JavaScript support in WebView is disabled by default, so you’ll need to enable it first. This is a precaution motivated by the fact that executing random JavaScript code can be risky from security standpoint.

To enable JavaScript execution inside WebView, execute this command:

webView.settings.javaScriptEnabled = true

After that, the button should work as expected.

Evaluate External JavaScript Code inside WebView

The previous example demonstrated execution of JavaScript code contained inside the original HTML. But what if you want to “push” JavaScript code from outside, at runtime? Enter webView.evaluateJavascript(script, resultCallback) method.

This code will change WebView’s background color to blue:

webView.evaluateJavascript("document.body.style.background = 'blue';", null)

Evaluate External JavaScript Code inside WebView and Return Value

In the previous example, I pushed some JavaScript code into the WebView and evaluated it. That was one-way communication. Is there a way to inject JavaScript code into the WebView and then return some data back to the native side? Yes, there is!

This code will pull the text from the showToast input element inside the WebView and show this text in a toast message:

webView.evaluateJavascript("(function() { return document.getElementById('toastMessage').value; })();") { returnValue ->
    Toast.makeText(requireContext(), returnValue, Toast.LENGTH_SHORT).show()
}

Note the more involved syntax of the JavaScript code here. In the previous example, I just wrote the JavaScript logic which I wanted to evaluate, but now I enclose this logic in an anonymous function and evaluate that function.

If you run the above code, you’ll notice that the text shown in the toast is enclosed in double quotes. That’s because the return value from the JavaScript function is “wrapped” into JSON value, JSON object or JSON array (depending on what the function actually returns) before it’s passed to your native callback. Therefore, you’ll need to “unwrap” the return value to get the original data.

Call JavaScript Function inside WebView from Native Android Code

Now imagine that you know that there is a specific JavaScript function inside the code executed by the WebView and you want to call it from outside. The solution is very similar, but, instead of defining your own function, simply specify the name of the function you want to call:

webView.evaluateJavascript("getToastMessage();") { returnValue ->
    Toast.makeText(requireContext(), returnValue, Toast.LENGTH_SHORT).show()
}

Pass Arguments into JavaScript Function inside WebView

When calling a predefined function inside a WebView, you might also want to pass additional arguments into it. The syntax to achieve that is quite straightforward:

webView.evaluateJavascript("concatenateStrings('${arg1}', '${arg2}');") { returnValue ->
    Toast.makeText(requireContext(), returnValue, Toast.LENGTH_SHORT).show()
}

Unfortunately, if the passed arguments will contain any special characters, you can run into problems. For example, if any of the arguments in the above example will contain the single quote character, the resulting JavaScript code will be invalid. In addition, if the arguments can be multi-line, you better watch out.

The solution to this problem is to escape all special characters in the arguments before you construct the JavaScript code. There is a lot of info about escaping JavaScript arguments out there, so, now, that you know about this pitfall, you can look it up.

Call Native Android Code from within WebView

So far, I demonstrated how to initiate various flows involving WebView’s contents from the native side. However, in some cases, you want to do the exact opposite and call to native code from within the WebView itself. That’s possible, but the solution is a bit more involved.

First, in your native code, you need to define the “native interface” that will be exposed to JavaScript code inside the WebView:

class MyJavascriptInterface(private val context: Context) {
    @JavascriptInterface
    fun showToast(message: String) {
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
    }
}

The mandatory @JavascriptInterface annotation designates the methods that JavaScript code will be able to “see”. The fact that this specific interface requires Context is just an example.

Once you defined this interface, you need to add it to the WebView:

webView.addJavascriptInterface(MyJavascriptInterface(requireContext()), "MyJavascriptInterface")

The first argument is the interface itself and the second argument is the name of that instance inside the WebView. It’s not required for them to be the same, but this is a reasonable default choice.

After the above two steps, you’ll be able to call methods on this “native interface” from within JavaScript code inside the WebView:

function showToast() {
    MyJavascriptInterface.showToast(getToastMessage());
}

The method MyJavascriptInterface.showToast(message) in your native code will be called on a special background thread, so it must be thread-safe.

Access WebView’s JavaScript Console Messages in Native Android Code

If you’ll be working with a WebView in Android, chances are that you’ll want to read its console messages. This is a nice tool for logging and basic debugging.

However, if you’ll just add this statement inside your JavaScript code, you won’t see any output:

function concatenateStrings(arg1, arg2) {
    console.log(arg1, arg2);
    ...
}

To intercept console messages inside the native code, you’ll need to use the following trick:

webView.webChromeClient = object: WebChromeClient() {
    override fun onConsoleMessage(message: String?, lineNumber: Int, sourceID: String?) {
        Log.i("WebViewConsole", message)
    }
}

In this case, I simply print console messages into Android’s logcat, but you can do whatever you want with them.

Android Lifecycles Course

Get comfortable with lifecycles, which are one of the trickiest aspects of Android development.

Go to Course

Conclusion

WebView is relatively uncommon component in Android applications, but it does have its use cases. The integration between the native code and the contents of the WebView can be tricky, so, if you need WebView in your application, I hope that this article will help you on your journey.

Check out my premium

Android Development Courses

Leave a Comment