Jetpack Compose: WebView ProgressIndicator

Introduction

This Android Kotlin example demonstrates the integration of a WebView with a ProgressIndicator in a Jetpack Compose UI. Jetpack Compose, the modern toolkit for building native Android UIs, allows developers to create responsive and dynamic interfaces with less boilerplate code. Here, we see how to embed a WebView within a Compose layout and manage its loading state with a circular progress indicator, providing users visual feedback during webpage loading.

In this tutorial, we will break down the components and logic used in the code, focusing on the UI elements, state management, and how the WebView client interacts with the rest of the composable elements. By the end, you'll understand how Jetpack Compose can simplify embedding traditional Android views, like WebView, into declarative UI components.

Scaffold Layout

The code begins by defining the main user interface inside a Scaffold composable, which serves as the base structure of the UI. The Scaffold provides a consistent layout with built-in support for placing elements such as a TopAppBar and a content section. In this case, the TopAppBar displays the title "Compose - WebView ProgressIndicator," providing a polished and organized header for the app. The background color of the TopAppBar is a soft green (Color(0xFFC0E8D5)), adding a pleasing visual touch to the UI.

The content of the scaffold is handled by another composable, MainContent(), which is where the core functionality of the WebView and progress indicator is placed. The scaffold's overall background color is a neutral tone (Color(0xFFEDEAE0)), creating a clean and minimalistic backdrop for the UI components.

State Management

State management in Jetpack Compose is achieved using remember and mutableStateOf functions, which allow the UI to react to changes in data. In this example, two states are declared: url, which holds the current URL of the WebView, and visibility, which controls the visibility of the CircularProgressIndicator.

  • url: The initial value is set to "https://www.google.com", and this state is updated whenever a user selects a different website (Google or Yahoo) via the buttons.
  • visibility: A boolean state that tracks whether the progress indicator should be displayed. It is set to true when the webpage starts loading and switched to false once the loading completes.

By utilizing Compose’s reactive nature, the UI automatically updates when these state variables change, eliminating the need for manual UI refreshes, which is common in traditional Android development.

User Interaction and Buttons

Inside the MainContent() composable, a Row layout is used to arrange two buttons horizontally. These buttons allow users to switch between two preset URLs: Google and Yahoo. When a button is clicked, the url state is updated with the respective URL, which triggers the WebView to load the new page.

The buttons are placed inside a nested Row and are styled with padding and rounded corners (RoundedCornerShape(12.dp)). This adds to the overall polished look of the UI, ensuring that the buttons are both functional and aesthetically pleasing.

WebView Integration with AndroidView

Jetpack Compose provides the AndroidView composable, which allows developers to integrate traditional Android views within Compose layouts. In this example, a WebView is embedded inside a Box layout, and its loading state is managed by a custom WebViewClient.

  • The WebView is created using the factory lambda within AndroidView, where its settings (such as JavaScript support) and layout parameters are configured.
  • A WebViewClient is defined to handle the events onPageStarted and onPageFinished. When a webpage starts loading, visibility.value is set to true, causing the CircularProgressIndicator to appear. Once the page finishes loading, visibility.value is set to false, hiding the progress indicator.

This integration between the WebView's loading state and the composable elements showcases how easily traditional views can be controlled through Jetpack Compose's reactive UI paradigm.

Progress Indicator

The progress indicator is a CircularProgressIndicator, displayed only when the visibility state is set to true. It is placed next to the buttons, giving users immediate feedback that a page is loading. The progress indicator’s color is a striking blue (Color(0xFF0018A8)), ensuring it stands out against the neutral background.

The logic controlling the progress indicator is simple yet effective. Since the WebView's loading process is asynchronous, the use of onPageStarted and onPageFinished events ensures that the progress indicator is accurately synchronized with the WebView’s state.

Summary

This Kotlin example demonstrates how Jetpack Compose can be used to seamlessly integrate Android views like WebView into a declarative UI framework. By utilizing the Scaffold structure, state management, and AndroidView, this example provides a clean and responsive interface with minimal boilerplate code. The addition of the CircularProgressIndicator enhances user experience by visually signaling webpage loading.

Incorporating traditional views within Compose allows for a smooth transition for developers moving from imperative UI paradigms to the declarative world of Jetpack Compose. This example showcases how both worlds can coexist and complement each other, providing a powerful toolkit for modern Android app development.


MainActivity.kt

package com.cfsuman.jetpackcompose

import android.graphics.Bitmap
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.LinearLayout
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView


class MainActivity : AppCompatActivity() {
    override fun onCreate(savedObjectState: Bundle?) {
        super.onCreate(savedObjectState)
        setContent {
            GetScaffold()
        }
    }


    @Composable
    fun GetScaffold(){
        Scaffold(
            topBar = {
                TopAppBar(
                    title = { Text(
                        text = "Compose - WebView ProgressIndicator"
                    )},
                    backgroundColor = Color(0xFFC0E8D5),
                )
            },
            content = {MainContent()},
            backgroundColor = Color(0xFFEDEAE0),
        )
    }


    @Composable
    fun MainContent(){
        val url = remember { mutableStateOf("https://www.google.com")}
        val visibility = remember { mutableStateOf(false)}

        Box(
            modifier = Modifier.fillMaxSize(),
        ){
            Column(
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Row(
                    modifier = Modifier
                        .padding(8.dp),
                    horizontalArrangement = Arrangement.spacedBy(12.dp),
                    verticalAlignment = Alignment.CenterVertically

                ) {
                    Row(
                        modifier = Modifier
                            .padding(8.dp)
                            .clip(RoundedCornerShape(12.dp))
                            .wrapContentHeight(Alignment.CenterVertically),
                        horizontalArrangement = Arrangement.spacedBy(12.dp),
                        verticalAlignment = Alignment.CenterVertically
                    ) {
                        Button(onClick = {
                            url.value = "https://www.google.com"
                        }) {
                            Text(text = "Google")
                        }
                        Button(onClick = {
                            url.value = "https://www.yahoo.com"
                        }) {
                            Text(text = "Yahoo")
                        }
                    }

                    if (visibility.value){
                        CircularProgressIndicator(
                            color = Color(0xFF0018A8)
                        )
                    }
                }

                Box(
                    modifier = Modifier
                        .weight(2F)
                ) {
                    AndroidView(factory = { context ->
                        WebView(context).apply {
                            layoutParams = LinearLayout.LayoutParams(
                                LinearLayout.LayoutParams.MATCH_PARENT,
                                LinearLayout.LayoutParams.MATCH_PARENT
                            )
                            settings.javaScriptEnabled = true

                            webViewClient = object: WebViewClient(){
                                override fun onPageStarted(
                                    view: WebView, url: String,
                                    favicon: Bitmap?) {

                                    visibility.value = true
                                }

                                override fun onPageFinished(
                                    view: WebView, url: String) {
                                    visibility.value = false
                                }
                            }

                            loadUrl("https://www.google.com")
                        }
                    },update = {
                        it.loadUrl(url.value)
                    })
                }
            }
        }
    }
}
More android jetpack compose tutorials