Jetpack Compose: WebView progress percentage

Introduction

In the world of Android development, Jetpack Compose has quickly become a favored tool for building modern, responsive UIs. It simplifies the UI code by offering declarative syntax and allowing developers to focus more on building a seamless user experience. However, integrating features that interact with the native Android components, like WebView, can still be a bit challenging for developers who are more accustomed to the imperative approach. In this example, we will explore how to display a WebView with a progress indicator using Jetpack Compose. This tutorial walks through a simple app that displays the page load progress as a percentage when using WebView in Jetpack Compose.

This example uses a combination of Jetpack Compose for UI and Android's native WebView to load and display web pages. We will break down the code to understand how the scaffold structure works in Compose, how to integrate native Android components (WebView), and how to show the loading progress dynamically using a circular progress bar.

MainActivity Overview

The MainActivity class inherits from AppCompatActivity, and its onCreate method calls setContent, which is a Jetpack Compose function to define the app’s UI content. The UI is defined using composable functions that construct and render the elements of the application. The function GetScaffold() is the main entry point for the composable structure, and it encapsulates the app’s toolbar and the web content area.

Scaffold and TopAppBar

The Scaffold composable provides a structure with slots for different UI elements such as a TopAppBar and a content area. Here, the TopAppBar is used to display a simple title "Compose - WebView Progress Percentage." This bar has a background color set to a light greenish shade using the Color(0xFFC0E8D5) hex code. The Scaffold function also sets a background color for the entire screen and passes MainContent() to its content slot, which holds the main part of the user interface.

MainContent and State Management

The MainContent() composable is where the core logic of the WebView and progress indicator lives. Several key state variables are defined using the remember and mutableStateOf functions. These variables help track the URL to be loaded (url), the visibility of the progress indicator (visibility), and the current progress value (progress). These states are reactive and are updated as the WebView loads pages.

The UI elements in MainContent() are arranged using Jetpack Compose's Box and Column layouts. The Row layout is used to align the buttons and the progress indicator horizontally. There are two buttons labeled "MSN" and "Yahoo," which, when clicked, change the url state, triggering the WebView to load the corresponding page.

WebView and Progress Handling

The WebView is integrated into Jetpack Compose through the AndroidView composable, which allows you to use native Android views within a Compose layout. Inside AndroidView, a WebView is created and configured. The JavaScript support is enabled via settings.javaScriptEnabled = true, allowing modern websites to function properly.

To handle the web page loading process, two listeners are implemented. The WebViewClient is responsible for detecting when a page starts and finishes loading. When a page starts, the onPageStarted callback is triggered, setting the visibility of the progress indicator to true. When the page finishes, onPageFinished sets the visibility back to false.

The WebChromeClient is used to track the actual page load progress. The onProgressChanged callback monitors the progress, updating the progress state, which is displayed to the user as a percentage. As the progress changes, the UI automatically re-renders to reflect the new value in both the circular progress indicator and the percentage text next to it.

Displaying Progress to the User

The progress indicator consists of a CircularProgressIndicator composable, which shows a spinning circle, and a Text composable displaying the percentage value. These elements only become visible when visibility.value is true, which happens during page loading. The percentage value is rounded using roundToInt() to avoid floating-point values, offering a more user-friendly display.

Summary

This Jetpack Compose example demonstrates how to seamlessly integrate Android's native WebView within a modern declarative UI framework. The app is built around state management in Compose to handle URL changes, display a circular progress indicator, and show the current loading percentage. By using AndroidView, developers can bridge the gap between native Android components and Jetpack Compose, enhancing flexibility.

In summary, this project offers a straightforward solution for developers looking to combine Jetpack Compose's power with native views, providing a polished and responsive UI. It serves as a great starting point for anyone aiming to integrate complex, interactive components like WebView in a Compose-based Android app.


MainActivity.kt

package com.cfsuman.jetpackcompose

import android.graphics.Bitmap
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.webkit.WebChromeClient
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import kotlin.math.roundToInt


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


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


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

        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.msn.com"
                        }) {
                            Text(text = "MSN")
                        }
                        Button(onClick = {
                            url.value = "https://www.yahoo.com"
                        }) {
                            Text(text = "Yahoo")
                        }
                    }

                    if (visibility.value){
                        CircularProgressIndicator(
                            color = Color(0xFFE30022)
                        )
                        Text(
                            text = "${progress.value.roundToInt()}%",
                            fontWeight = FontWeight.Bold
                        )
                    }
                }

                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
                                }
                            }

                            // Set web view chrome client
                            webChromeClient = object: WebChromeClient(){
                                override fun onProgressChanged(
                                    view: WebView, newProgress: Int) {
                                    progress.value = newProgress.toFloat()
                                }
                            }

                            loadUrl(url.value)
                        }
                    },update = {
                        it.loadUrl(url.value)
                    })
                }
            }
        }
    }
}
More android jetpack compose tutorials