Turn a Website into a Secure Android App with Jetpack Compose and Kotlin

If you’ve ever wanted to turn a website into an Android app, Jetpack Compose and Kotlin make it surprisingly straightforward. With a few lines of code, you can have a functional app that displays your website. However, when it comes to embedding websites, security should always be a top priority. In this post, I’ll walk you through the steps to create a secure website viewer app, focusing on both functionality and best practices for safety.

Thank me by sharing on Twitter 🙏

Laying the Groundwork for an Android Mobile App

To get started, you’ll need an Android project with Jetpack Compose enabled. I won’t go into setting up the project itself, but once you have an empty Compose activity, you’re ready to begin. The goal is to use a WebView to display your website while minimizing potential vulnerabilities.

Create a new project with the Empty Activity template. I used API 24:

The first step is granting your app the necessary permissions. Since your app will access the internet to load the website, add the following permission in your AndroidManifest.xml file:

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

With this in place, we can move on to embedding the website securely.

Embedding the Website with WebView

Jetpack Compose doesn’t have a built-in WebView component, but it does provide AndroidView, which lets you embed traditional Android views. Here’s how you can set up a basic WebView:

Kotlin
@Composable
fun WebViewScreen(url: String) {
    AndroidView(
        factory = { context ->
            WebView(context).apply {
                settings.javaScriptEnabled = true // Enable JavaScript for interactive websites
                settings.domStorageEnabled = true // Support for DOM storage
                webChromeClient = WebChromeClient()
                webViewClient = WebViewClient()
                loadUrl(url)
            }
        },
        modifier = Modifier.fillMaxSize()
    )
}

Your MainActivity would look like this:

Kotlin
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            WebAppTheme {
                WebViewScreen("https://example.com")
            }
        }
    }
}

This basic setup works, but it’s not enough to ensure security. By default, WebView allows behaviors that could expose your app to risks. Let’s take steps to address these vulnerabilities.

Enhancing Security

Security in a WebView app involves both configuration and a few additional precautions. Below are the key changes I recommend implementing.

NOTE: You should absolutely review the potential Security Risks Android Guide before releasing you app to production. I may have missed security details in this post that you should implement.

Enforce HTTPS Traffic

Ensuring that your app communicates only over secure HTTPS connections is critical. To enforce this, update your app’s AndroidManifest.xml file by setting usesCleartextTraffic to false and referencing a custom network security configuration:

XML
<application
    android:usesCleartextTraffic="false"
    android:networkSecurityConfig="@xml/network_security_config"
    ...
>
  ...
</application>

Next, create a network_security_config.xml file under res/xml/ with the following content:

XML
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="false">
        <domain includeSubdomains="true">example.com</domain>
    </domain-config>
</network-security-config>

Replace example.com with your website’s domain. This ensures your app only permits encrypted HTTPS traffic.

Disable Unsafe WebView Settings

WebView includes settings that, if left enabled, can make your app vulnerable. For example, allowing file access or universal file access can expose sensitive data or make your app a target for malicious code.

Here’s how to disable these settings:

Kotlin
@Composable
fun WebViewScreen(url: String) {
    AndroidView(
        factory = { context ->
            WebView(context).apply {
                settings.javaScriptEnabled = true
                settings.domStorageEnabled = true

                // Disable unsafe settings
                settings.allowFileAccess = false
                settings.allowContentAccess = false
                settings.allowFileAccessFromFileURLs = false
                settings.allowUniversalAccessFromFileURLs = false

                webChromeClient = WebChromeClient()
                webViewClient = WebViewClient()
                loadUrl(url)
            }
        },
        modifier = Modifier.fillMaxSize()
    )
}

Disabling these features ensures that your WebView can’t be exploited to access local files or content.

Validate URLs

One way attackers can exploit WebView is by redirecting users to malicious websites. You can guard against this by validating URLs before they load. Override the shouldOverrideUrlLoading method in your WebViewClient to allow only trusted URLs:

Kotlin
webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
        val url = request?.url.toString()
        return if (url.startsWith("https://example.com")) {
            false // Allow loading
        } else {
            true // Block loading
        }
    }
}

Replace https://example.com with your domain. This ensures your app won’t navigate to untrusted sites.

Adding a Progress Indicator

To improve user experience, consider adding a loading indicator while the WebView is loading the page. You can use LinearProgressIndicator in Compose to show progress based on the WebView’s loading status:

Kotlin
@Composable
fun WebViewWithProgressBar(url: String) {
    var progress by remember { mutableStateOf(0) }

    Column(modifier = Modifier.fillMaxSize()) {
        if (progress < 100) {
            LinearProgressIndicator(progress / 100f, modifier = Modifier.fillMaxWidth())
        }

        AndroidView(
            factory = { context ->
                WebView(context).apply {
                    settings.javaScriptEnabled = true
                    webChromeClient = object : WebChromeClient() {
                        override fun onProgressChanged(view: WebView?, newProgress: Int) {
                            progress = newProgress
                        }
                    }
                    webViewClient = WebViewClient()
                    loadUrl(url)
                }
            },
            modifier = Modifier.weight(1f)
        )
    }
}

This addition makes the app feel more responsive, especially for slower-loading websites.

Testing Your App

Once you’ve implemented these changes, thoroughly test your app to ensure it behaves as expected. Pay close attention to how it handles:

  • Invalid or untrusted URLs
  • Loading speed and responsiveness
  • HTTPS-only enforcement

Testing on multiple devices and network conditions can help uncover potential issues.

Wrapping Up

Creating an Android app to display a website is simple with Jetpack Compose and Kotlin, but ensuring that app is secure takes additional effort. By enforcing HTTPS traffic, disabling unsafe settings, validating URLs, and enhancing user experience with a progress indicator, you’ll not only deliver a functional app but also protect your users from potential threats. These steps are an investment in your app’s reliability and security, and they’ll go a long way in building trust with your audience.

Share this:

Leave a Reply