Android WebView The Missing guide

Mastering sending/receiving data to/from android web views. How we interact with them. A fully functional app inside 🌟 Source code inside 👌

· 5 min read
Android WebView The Missing guide

Doesn't webview support a feature you, or your team wants? it Doesn't matter anymore, we will support it together.

Since we are busy developers I will not waste your time.

If you want to do any of the following:

  1. Send data to some web app from Android.
  2. Receive data from some web app from Android.

Then this article is for you, keep reading, else you can read it too, it won't hurt.

Sending Data to Web App

Let's say I've some web app I need to pass some data to it, for example:

  • Calling JavaScript function
  • Sending string or JSON object

Okay, so let's begin

Creating a webview

I have the following XML layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".HomeFragment">

    <WebView
        android:id="@+id/web_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text="@string/home_fragment_label"
        app:layout_constraintBottom_toTopOf="@+id/guideline"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent=".65" />

    <TextView
        android:id="@+id/tvTitle"
        style="@style/TextAppearance.AppCompat.Title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="24dp"
        android:layout_marginEnd="16dp"
        android:text="@string/we_can_send_data_to_the_web_app"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/web_view" />

    <EditText
        android:id="@+id/etMessage"
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:autofillHints="name"
        android:hint="@string/message_to_be_sent"
        android:inputType="textAutoComplete"
        android:textColorHint="#757575"
        app:layout_constraintEnd_toStartOf="@+id/btnSend"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvTitle" />

    <Button
        android:id="@+id/btnSend"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:text="@string/send"
        app:layout_constraintBottom_toBottomOf="@+id/etMessage"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/etMessage" />

</androidx.constraintlayout.widget.ConstraintLayout>
XML code layout 

Also, don't forget to add the INTERNET permission

    <uses-permission android:name="android.permission.INTERNET" />
internet permission XML snippet

Let's enable Javascript on the webview and load my custom page 😃 that I'm hosting on my GitHub account.

binding.webview.apply {
	settings.javaScriptEnabled = true
    loadUrl("https://abd3lraouf.github.io/webview_code_injection_from_android")
}
enable javascript on the web view

Let's run the app

Code injection

Now we are ready, let's begin code injection

Let's say I have a Javascript function as follows:

function messageMe(message){
  document.getElementById("demo").innerHTML = message;
}
the target javascript function which needs to be called

And of course, you can predict the DOM

    <body>
        <h1>Code injection</h1>
        <button>Launch Native Code</button>
        <h2> Messages from Android</h2>
        <p id="demo">Can you replace me?</h2>
    </body>
simple webpage HTML code 

What we need to do is just execute the following JavaScript code

messageMe("Message")
The javascript code that we shall execute

Luckily, Android does give us a way to execute javascript code on any web app we open from a webview using the evaluateJavascript function from the WebView.

So what we need to do is just call this function with a correctly parsed JavaScript code.

evaluateJavascript("messageMe(\"$message\")", null)
the kotlin code used to call javascript code from android

Let's try it

Now we are done. Let's go deeper with the next step, receiving data from a web app.

Receiving Data from Web App

Let's say I've some web app that needs to deliver some data to the android side, such as the following:

  • Sending a string
  • Triggering some native code
  • Navigating to another screen from the android side

To implement such features we need to do the following

Create a javascript interface

A javascript interface is just a kotlin interface with one or more functions annotated with the @JavascriptInterface annotation, this function will be visible from the javascript side.

Now Let's  say I want to display a native toast message from the web app

    @JavascriptInterface
    @Suppress("unused")
    fun toast(message: String) {
        Toast.makeText(context, message, Toast.LENGTH_LONG).show()
    }
the javascript interface used to display a toast

Also for javascript to call this function, it must be inside some object.

Android = {}; Android.toast = function(message) { JsMediator.toast(message); }
the javascript code used to call the toast function on the JsMediator object

In the snippet above I create an empty object called Android then defined functions inside it with the name toast, this function takes a parameter message which calls it on another Object called JsMediator which is the ring that binds our javascript code to the kotlin code. JsMediator is the key I'll bind the javascript interface on. woohoo.

You may read the last paragraph again, I know it's difficult to wrap your head around it.

        /**
         * called before start loading the page
         */
        fun injectMediator(webView: WebView) {
            webView.evaluateJavascript(
                "Android = {}; Android.toast = function(message) { JsMediator.toast(message); }",
                null
            )
        }
the actual code

Now, when shall we call this method?

Binding the interface

Like any other android view setup, we need it to be done after the view creation. So we will bind the interface in the onCreate  in activities or the onViewCreated in fragments.

This step is required for the javascript world to know that I have a method called toast

        binding.webView.apply {
            settings.javaScriptEnabled = true
            setWebContentsDebuggingEnabled(true)
            JavaScriptShareInterface.bind(requireContext(), this)
            loadUrl("https://abd3lraouf.github.io/webview_code_injection_from_android")
        }
binding the javascript interface

And the bind function attaches the interface on the key JsMediator

        /**
         * called at onCreate to initiate code injection
         */
        fun bind(context: Context, webView: WebView) {
            webView.addJavascriptInterface(JavaScriptShareInterface(context), "JsMediator")
        }
attaches the interface

The final step

I want to intercept the page loading step and inject the mediator to forward the javascript calls for Android.toast(message) to the mediator, we defined above

So we will create a CustomWebViewClient

    object CustomWebViewClient : WebViewClient() {
        override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
            super.onPageStarted(view, url, favicon)
            JavaScriptShareInterface.injectMediator(view)
        }
    }
inject the mediator

This code will inject the mediator after the page is started loading.

The final setup code for the web view will be

        binding.webView.apply {
            settings.javaScriptEnabled = true
            setWebContentsDebuggingEnabled(true)
            JavaScriptShareInterface.bind(requireContext(), this)
            webViewClient = CustomWebViewClient

            loadUrl("https://abd3lraouf.github.io/webview_code_injection_from_android")
        }
The final setup code for the web view

Now we are not done yet.

We are all good Android Citizens, we all should clear resources after usage, this part is easy, just bind the interface and unbind it in proper view lifecycle methods.

Also, I applied some of the clean code principles to the code. All of that and more, I will leave this part for you to see for yourself.

And that's the moment, I have to end this article at. All the code I used for this tutorial is hosted at my GitHub account 🤗🤗

  1. The Android APP is HERE
  2. The Web App is HERE

Conclusion

in this article, I talked about how we can use web views for our benefit and how to interact with them.

Please be aware that the Webview is a double-edged weapon, we must pay attention when we use them, or even execute javascript on them. That's where most vulnerabilities come from. Please be careful and Happy coding.

Happy Coding

AbdElraouf Sabri