Home Android webviews the missing guide
Post
Cancel

Android webviews the missing guide

Mastering sending/receiving data to/from android web views. How we interact with them.

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.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?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>

Also, don’t forget to add the INTERNET permission

1
    <uses-permission android:name="android.permission.INTERNET" />

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

1
2
3
4
binding.webview.apply {
	settings.javaScriptEnabled = true
    loadUrl("https://abd3lraouf.github.io/webview_code_injection_from_android")
}

Let’s run the app

Initial app version Initial app version

Code injection

Now we are ready, let’s begin code injection

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

1
2
3
function messageMe(message){
  document.getElementById("demo").innerHTML = message;
}

And of course, you can predict the DOM

1
2
3
4
5
6
    <body>
        <h1>Code injection</h1>
        <button>Launch Native Code</button>
        <h2> Messages from Android</h2>
        <p id="demo">Can you replace me?</h2>
    </body>

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

1
messageMe("Message")

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.

1
evaluateJavascript("messageMe(\"$message\")", null)

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

1
2
3
4
5
@JavascriptInterface
@Suppress("unused")
fun toast(message: String) {
    Toast.makeText(context, message, Toast.LENGTH_LONG).show()
}

Also for javascript to call this function, it must be inside some object and let’s call the toast function on the JsMediator object.

1
Android = {}; Android.toast = function(message) { JsMediator.toast(message); }

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.

1
2
3
4
5
6
7
/**
* called before start loading the page
*/
fun injectMediator(webView: WebView) {
    val code = "Android = {}; Android.toast = function(message) { JsMediator.toast(message); }"
    webView.evaluateJavascript(code, null)
}

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

1
2
3
4
5
6
binding.webView.apply {
    settings.javaScriptEnabled = true
    setWebContentsDebuggingEnabled(true)
    JavaScriptShareInterface.bind(requireContext(), this)
    loadUrl("https://abd3lraouf.github.io/webview_code_injection_from_android")
}

And the bind function attaches the interface on the key JsMediator

1
2
3
4
5
6
/**
* called at onCreate to initiate code injection
*/
fun bind(context: Context, webView: WebView) {
    webView.addJavascriptInterface(JavaScriptShareInterface(context), "JsMediator")
}

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

1
2
3
4
5
6
object CustomWebViewClient : WebViewClient() {
    override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
        super.onPageStarted(view, url, favicon)
        JavaScriptShareInterface.injectMediator(view)
    }
}

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

The final setup code for the web view will be

1
2
3
4
5
6
7
8
binding.webView.apply {
    settings.javaScriptEnabled = true
    setWebContentsDebuggingEnabled(true)
    JavaScriptShareInterface.bind(requireContext(), this)
    webViewClient = CustomWebViewClient

    loadUrl("https://abd3lraouf.github.io/webview_code_injection_from_android")
}

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 🤗🤗.

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

This post is licensed under CC BY 4.0 by the author.
Contents

-

-

Trending Tags