Android
WebView
JavaScript
Mobile Development
Android Development

Android Calling JavaScript functions in WebView

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Calling JavaScript from an Android WebView is straightforward in demos, but production apps need strict timing and security controls. Calls can fail if the page is not ready, arguments can break JavaScript parsing if not escaped, and untrusted content can turn bridge features into security risk. A robust approach treats WebView messaging like an API contract with lifecycle rules.

Core Sections

1. Choose the right call mechanism by Android version

There are two common methods:

  • evaluateJavascript for Android KitKat and later. This is preferred and supports a callback result.
  • loadUrl("javascript:...") for compatibility with older devices.

In most modern apps, use evaluateJavascript and keep a compatibility fallback only if required by your minimum SDK policy.

kotlin
1private fun callJs(webView: WebView, script: String, onResult: (String?) -> Unit = {}) {
2    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
3        webView.evaluateJavascript(script) { result -> onResult(result) }
4    } else {
5        webView.loadUrl("javascript:$script")
6        onResult(null)
7    }
8}

This wrapper centralizes behavior and keeps call sites consistent.

2. Wait for page readiness before invoking JavaScript

Most failures happen because Android calls JavaScript too early. The page may still be loading or script bundles may not be initialized. Gate calls on onPageFinished and maintain a queue for pending commands.

kotlin
1class WebBridgeController(private val webView: WebView) {
2    private val pending = ArrayDeque<String>()
3    private var ready = false
4
5    fun onPageFinished() {
6        ready = true
7        while (pending.isNotEmpty()) {
8            callJs(webView, pending.removeFirst())
9        }
10    }
11
12    fun invoke(script: String) {
13        if (ready) callJs(webView, script) else pending.addLast(script)
14    }
15}

This prevents race conditions during startup and page reload.

3. Serialize arguments safely

Do not build JavaScript calls by string concatenation of raw user input. Special characters can break script syntax. Serialize payloads as JSON and parse them on the JavaScript side.

kotlin
1import org.json.JSONObject
2
3fun sendMessage(controller: WebBridgeController, text: String) {
4    val payload = JSONObject().put("message", text).toString()
5    controller.invoke("window.appReceive($payload)")
6}
javascript
1window.appReceive = function(payload) {
2  const data = typeof payload === "string" ? JSON.parse(payload) : payload;
3  console.log("Message from Android:", data.message);
4};

JSON payloads are easier to evolve than positional argument strings.

4. Handle return values and failures explicitly

evaluateJavascript returns JSON-encoded results. Treat parse errors and null results as expected failure modes, not impossible cases.

Use one helper for parsing:

  • decode quoted strings
  • map JavaScript null to nullable Kotlin values
  • log parse failures with script identifiers

This makes telemetry actionable when web bundle changes break bridge contracts.

5. Security controls for production

If your WebView can load remote content, keep JavaScript bridge exposure minimal. Recommendations:

  • Allow only trusted origins for pages that receive privileged calls.
  • Avoid exposing broad addJavascriptInterface objects unless required.
  • Disable file access features unless needed.
  • Keep bridge methods narrow and input-validated.

Security issues in WebView bridges are usually architectural, not syntactic. Restrict capabilities first, then optimize ergonomics.

6. Testing strategy

Use layered tests:

  • unit tests for script-building helpers
  • instrumentation tests that verify call timing and result parsing
  • failure tests for page reload, timeout, and invalid payload

Keep one smoke test that loads a small local HTML page in instrumentation and confirms round-trip communication. This catches regressions from both app code and WebView behavior changes.

Common Pitfalls

  • Calling JavaScript before page scripts are ready and losing messages silently.
  • Concatenating raw input into script text and creating parse or injection issues.
  • Ignoring callback result parsing details from evaluateJavascript.
  • Exposing large Android interfaces to untrusted web content.
  • Mixing multiple ad hoc bridge patterns instead of one contract-driven wrapper.

Summary

  • Use evaluateJavascript as the primary call path, with fallback only when needed.
  • Gate calls on page readiness and queue commands during load.
  • Serialize data as JSON to keep payload handling safe and maintainable.
  • Parse callback results defensively and log failures with context.
  • Treat WebView bridge design as a security boundary, not just a convenience API.

Course illustration
Course illustration

All Rights Reserved.