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

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

Jetpack Compose: How to use WebView

Introduction

Jetpack Compose has become a popular framework for developing Android applications with its modern declarative approach. Despite its flexibility, there are still some scenarios where developers need to integrate traditional Android views, such as WebView, into their Compose-based UI. This can be achieved by combining the power of Jetpack Compose with Android's AndroidView interop feature, allowing developers to embed native Android views within a Compose layout. In this article, we’ll walk through how to use a WebView inside a Jetpack Compose project and provide details on how to configure it effectively.

This Kotlin example demonstrates the use of WebView within a Jetpack Compose UI. We will break down the code into sections, explaining the setup, how to integrate WebView within Compose, and how to apply necessary settings for a smooth browsing experience. Let’s take a closer look at how this is done.

Main Structure: Setting Up Compose Scaffold

The entry point for the app is MainActivity, which inherits from AppCompatActivity. The onCreate() method is overridden to set the content using Compose’s setContent method. Inside this method, a composable function GetScaffold() is called, which serves as the main layout of the app. This layout consists of a Scaffold, a composable that provides a basic structure for a screen. The Scaffold includes a TopAppBar with a title and a content section that loads the MainContent() function.

The TopAppBar is styled with a custom background color using Color(0xFFC0E8D5), and the title text "Compose - Using WebView" is displayed. The background color for the overall content area is also customized, giving the UI a clean, modern look.

Main Content: Integrating WebView

In the MainContent() composable function, the first key element is the remember function, which stores the state of the URL the WebView will load. This state, represented by url, allows dynamic updates of the URL when the user interacts with buttons. The Box and Column composables are used to arrange the layout, where the Box takes up the majority of the screen and holds the WebView.

To integrate WebView into Jetpack Compose, the AndroidView composable is used. This allows embedding any Android view (such as WebView) into a Compose layout. The AndroidView’s factory parameter creates the WebView instance, and the applySettings() extension method is called to configure the WebView's behavior. Additionally, the update parameter of AndroidView reloads the URL whenever the url state changes, ensuring the correct page is loaded based on user interactions.

Button Controls: Switching Between URLs

The bottom section of the UI features a row of buttons that allow the user to navigate between different websites. This is done using a Row composable that contains three buttons: one for Google, one for Yahoo, and another for Example.com. Each button is associated with an onClick listener that updates the url state with the corresponding URL. Once the url state is updated, the WebView in the AndroidView automatically reloads the new URL.

The buttons are styled with padding and rounded corners using RoundedCornerShape, ensuring a visually appealing and user-friendly interface. The use of Arrangement.spacedBy(12.dp) ensures proper spacing between the buttons.

Applying WebView Settings

The applySettings() function is an extension method that configures various settings for the WebView. These settings are essential for improving user experience and compatibility with modern web standards. For example, JavaScript is enabled with settings.javaScriptEnabled = true, and caching is set up using setAppCacheEnabled(true) and setAppCachePath(), which helps optimize loading times.

Other important configurations include enabling zoom controls, setting text zoom to 100%, and ensuring that images are loaded automatically by setting loadsImagesAutomatically = true. The method also includes checks for Android version compatibility, such as enabling safe browsing for devices running Android Oreo (API level 26) and above.

Additionally, the method enables important features like DOM storage, geolocation, multiple windows, and hardware acceleration. These settings help make the WebView behave more like a fully functional browser, handling complex web pages effectively.

Summary

This Kotlin example demonstrates how to integrate a WebView into a Jetpack Compose-based UI using the AndroidView composable. By managing the URL state and applying comprehensive settings through an extension method, the WebView offers a robust browsing experience within the app. The use of buttons to switch between URLs adds interactivity, and the overall structure of the code showcases the flexibility of combining Compose with traditional Android views.

In conclusion, this approach allows developers to maintain the declarative nature of Jetpack Compose while leveraging existing Android views like WebView. Whether you're building a web-based app or simply need to display web content, this example provides a clear and effective method for achieving that in your Compose projects.


MainActivity.kt

package com.cfsuman.jetpackcompose

import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.webkit.WebSettings
import android.webkit.WebView
import android.widget.FrameLayout
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 - Using WebView"
                    )},
                    backgroundColor = Color(0xFFC0E8D5),
                )
            },
            content = {MainContent()},
            backgroundColor = Color(0xFFEDEAE0),
        )
    }


    @Composable
    fun MainContent(){
        val url = remember { mutableStateOf("https://www.google.com")}
        Box(
            modifier = Modifier.fillMaxSize(),
        ){
            Column(
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Box(
                    modifier = Modifier
                        .weight(2F)
                ) {
                    AndroidView(factory = { context ->
                        WebView(context).apply {
                            applySettings()
                            loadUrl("https://www.google.com")
                        }
                    },update = {
                        it.loadUrl(url.value)
                    })
                }

                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")
                    }
                    Button(onClick = {
                        url.value = "https://example.com"

                    }) {
                        Text(text = "Example")
                    }
                }
            }
        }
    }
}


// extension method to apply web view settings
fun WebView.applySettings(){
    val layoutParams = LinearLayout.LayoutParams(
        LinearLayout.LayoutParams.MATCH_PARENT,
        LinearLayout.LayoutParams.MATCH_PARENT
    )
    // or use this
    val linearlayoutParams = FrameLayout.LayoutParams(
        FrameLayout.LayoutParams.MATCH_PARENT,
        FrameLayout.LayoutParams.MATCH_PARENT
    )
    this.layoutParams = layoutParams

    //clearCache(true)
    clearCache(true)

    // Get the web view settings instance
    val settings = settings

    // Enable java script in web view
    settings.javaScriptEnabled = true

    // Enable and setup web view cache
    settings.setAppCacheEnabled(true)
    settings.cacheMode = WebSettings.LOAD_DEFAULT
    settings.setAppCachePath(context.cacheDir.path)

    // Enable zooming in web view
    settings.setSupportZoom(false)
    settings.builtInZoomControls = true
    settings.displayZoomControls = true

    // Zoom web view text
    settings.textZoom = 100

    // Enable disable images in web view
    settings.blockNetworkImage = false
    // Whether the WebView should load image resources
    settings.loadsImagesAutomatically = true

    // More web view settings
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        settings.safeBrowsingEnabled = true  // api 26
    }

    //settings.pluginState = WebSettings.PluginState.ON
    settings.useWideViewPort = true
    settings.loadWithOverviewMode = true
    settings.javaScriptCanOpenWindowsAutomatically = true
    settings.mediaPlaybackRequiresUserGesture = false

    // More optional settings, you can enable it by yourself
    settings.domStorageEnabled = true
    settings.setSupportMultipleWindows(true)
    settings.loadWithOverviewMode = true
    settings.allowContentAccess = true
    settings.setGeolocationEnabled(true)
    settings.allowUniversalAccessFromFileURLs = true
    settings.allowFileAccess = true

    // WebView settings
    fitsSystemWindows = true
    setLayerType(View.LAYER_TYPE_HARDWARE, null)
}
More android jetpack compose tutorials

Jetpack compose: Room Add, Remove, Update

Jetpack Compose - Room Add, Remove, Update

This code demonstrates a basic Android application built with Jetpack Compose that utilizes Room for data persistence. The app manages a list of "Students" with functionalities to add, remove, and update them.

Key Components:

  • Room Database: The RoomSingleton class defines a Room database with a single entity - Student. It provides methods to access the DAO (Data Access Object) for interacting with student data.
  • Student Entity: The Student data class represents a student record with properties like ID (primary key), name (full name), and result (integer).
  • StudentDao: This interface defines methods for CRUD (Create, Read, Update, Delete) operations on student data within the database.
  • StudentViewModel: This ViewModel class acts as the single source of truth for student data. It holds a reference to the Room database instance and exposes functions to interact with students (insert, update, delete, clear all).
  • MainContent Composable: This function represents the UI of the app. It displays a list of students using LazyColumn and provides buttons for adding new students, clearing all students, editing individual students, and deleting them.

Summary

This example showcases how to integrate Jetpack Compose with Room to manage data in an Android application. It demonstrates live data updates, user interaction with the UI to manipulate data, and background operations using coroutines for database access. This provides a starting point for building more complex applications with persistent data storage and a reactive UI.


MainActivity.kt

package com.example.composeroomexample

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.runtime.Composable
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import java.util.*
import kotlin.random.Random

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { MainContent() }
    }
}


@Composable
fun MainContent(){
    val scope = rememberCoroutineScope()
    val model = viewModel<StudentViewModel>()
    val list:List<Student> = model.students.observeAsState(listOf()).value

    Column(
        modifier = Modifier.padding(12.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        Row(
            horizontalArrangement = Arrangement.spacedBy(12.dp)
        ){
            // add new student
            Button(onClick = {
                model.insert(
                    Student(
                        null,UUID.randomUUID().toString(),
                        Random.nextInt(10,90)
                    )
                )
            }) { Text(text = "Add Student") }

            // delete all students
            Button(onClick = { model.clear() }) {
                Text(text = "Clear")
            }
        }

        LazyColumn(
            verticalArrangement = Arrangement.spacedBy(8.dp),
            contentPadding = PaddingValues(vertical = 12.dp)
        ) {
            itemsIndexed(list) { index,item ->
                Card(
                    modifier = Modifier.fillMaxWidth(),
                    backgroundColor = Color(0xFFA1CAF1)
                ) {
                    Row(
                        modifier = Modifier.padding(12.dp),
                        verticalAlignment = Alignment.CenterVertically
                    ) {

                        Box(
                            Modifier.size(48.dp).clip(CircleShape)
                                .background(Color(0xFFF0FFFF)),
                            contentAlignment = Alignment.Center
                        ) {
                            Text(
                                text = "${item.id}",
                                style = MaterialTheme.typography.h6
                            )
                        }

                        Spacer(modifier = Modifier.width(12.dp))
                        Text(
                            text = item.fullName.take(10),
                            style = MaterialTheme.typography.caption
                        )

                        Spacer(modifier = Modifier.width(12.dp))
                        Text(
                            text = " ${item.result}",
                            style = MaterialTheme.typography.h5
                        )
                        Spacer(modifier = Modifier.weight(1F))

                        // edit button
                        IconButton(onClick = {
                            val updatedItem = Student(
                                item.id,item.fullName,Random.nextInt(0,100)
                            )
                            model.update(updatedItem)
                        }) { Icon(Icons.Filled.Edit,"")}

                        // delete button
                        IconButton(onClick = { model.delete(item)}) {
                            Icon(Icons.Filled.Delete,"")
                        }
                    }
                }
            }
        }
    }
}
RoomSingleton.kt

package com.example.composeroomexample

import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import android.content.Context

@Database(entities = [Student::class], version = 1, exportSchema = false)
abstract class RoomSingleton : RoomDatabase() {
    abstract fun studentDao():StudentDao

    companion object {
        private var INSTANCE: RoomSingleton? = null
        fun getInstance(context: Context): RoomSingleton {
            if (INSTANCE == null) {
                INSTANCE = Room.databaseBuilder(
                    context,
                    RoomSingleton::class.java,
                    "roomdb")
                    .build()
            }
            return INSTANCE as RoomSingleton
        }
    }
}
RoomEntity.kt

package com.example.composeroomexample

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "studentTbl")
data class Student(
    @PrimaryKey
    var id:Long?,

    @ColumnInfo(name = "uuid")
    var fullName: String,

    @ColumnInfo(name = "result")
    var result:Int
)
RoomDao.kt

package com.example.composeroomexample

import androidx.lifecycle.LiveData
import androidx.room.*

@Dao
interface StudentDao{
    @Query("SELECT * FROM studentTbl ORDER BY id DESC")
    fun getStudents():LiveData<MutableList<Student>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(student:Student)

    @Update
    suspend fun update(student:Student)

    @Delete
    suspend fun delete(student:Student)

    @Query("DELETE FROM studentTbl")
    suspend fun clear()
}
StudentViewModel.kt

package com.example.composeroomexample

import android.app.Application
import androidx.lifecycle.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch


class StudentViewModel(application:Application)
    : AndroidViewModel(application){
    
	private val db:RoomSingleton = RoomSingleton
        .getInstance(application)

    internal val students : LiveData<MutableList<Student>> =
        db.studentDao().getStudents()

    fun insert(student: Student){
        viewModelScope.launch(Dispatchers.IO) {
            db.studentDao().insert(student)
        }
    }

    fun update(student: Student){
        viewModelScope.launch(Dispatchers.IO) {
            db.studentDao().update(student)
        }
    }

    fun delete(student: Student){
        viewModelScope.launch(Dispatchers.IO) {
            db.studentDao().delete(student)
        }
    }

    fun clear(){
        viewModelScope.launch(Dispatchers.IO) {
            db.studentDao().clear()
        }
    }
}
build.gradle [dependencies]

apply plugin: 'kotlin-kapt'

dependencies {
    def room_version = "2.4.2"
    implementation "androidx.room:room-runtime:$room_version"
    implementation "androidx.room:room-ktx:$room_version"
    kapt "androidx.room:room-compiler:$room_version"

    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0-alpha06"
    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-alpha06'
    implementation 'androidx.compose.runtime:runtime-livedata:1.2.0-alpha07'
}
More android jetpack compose tutorials