Mastering Android Webviews: The Missing Guide
Android webviews are a powerful tool for integrating web content into your Android applications. In this article, we’ll explore how to send and receive data between Android webviews and web apps, enabling seamless communication between the two platforms.
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:
- Send data to some web app from Android.
- 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
<?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
<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.
binding.webview.apply {
settings.javaScriptEnabled = true
loadUrl("https://abd3lraouf.github.io/webview_code_injection_from_android")
}
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;
}
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>
What we need to do is just execute the following JavaScript code
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.
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
@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.
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.
/**
* 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
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
/**
* 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
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
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