Introduction
This code demonstrates a Jetpack Compose application that utilizes Room for persistent data storage and manages employee data using a ViewModel. The user interface (UI) allows adding single or multiple employees, displaying a list of employees, and clearing the data.
Breakdown
The code is divided into several parts:
-
MainActivity.kt: This file defines the main activity of the application. It sets the content of the activity using a composable function
MainContent
. -
MainContent.kt: This composable function builds the UI of the app. It uses various Jetpack Compose components like
Column
,Row
,Button
,LazyColumn
, andCard
to display buttons, text, and a list of employees.- It utilizes a
viewModel
composable to access theEmployeeViewModel
. - It defines a state variable
textState
to hold potential filter text for the employee list. - It retrieves the list of employees from the ViewModel using
collectAsState
and logs a message on recomposition. - It displays two buttons, one to add 10 random employees and another to add a single random employee.
- It displays a button to clear the employee data and a text showing the number of employees, potentially filtered by
textState.value
. - It uses
LazyColumn
to display a list of employees in cards. Each card shows the employee's ID and name (truncated to 12 characters).
- It utilizes a
-
RoomSingleton.kt: This file defines a singleton class
RoomSingleton
that acts as a central access point to the Room database. It uses Room's database builder to create an instance of the database and provides a method to get the instance. -
RoomEntity.kt: This file defines a data class
Employee
that represents an employee with an ID (Long) and a full name (String). It is annotated with@Entity
to indicate it's a Room entity stored in a table named "employeeTbl". -
RoomDao.kt: This file defines an interface
EmployeeDao
that interacts with the Room database for employee data. It defines methods for:- Retrieving all employees (
allEmployees
). - Inserting a single employee (
insert
). - Inserting a list of employees (
insertAll
). - Clearing all employees (
clear
).
- Retrieving all employees (
-
EmployeeViewModel.kt: This class defines a ViewModel named
EmployeeViewModel
that manages the employee data and interacts with the Room database.- It retrieves the instance of
RoomSingleton
using the application context. - It defines a
_Employees
state flow to hold the list of employees. - It exposes a
employees
state flow as a read-only view of_Employees
. - In the init block, it calls
loadEmployees
to fetch employees from the database. loadEmployees
is a suspend function launched in a coroutine scope to fetch employees and update_Employees
.- It provides functions for inserting a single employee (
insert
), inserting a list of employees (insertAll
), and clearing all employees (clear
). These functions launch coroutines to interact with the database and update_Employees
after the operation. - The
effect
function is a helper function used to launch coroutines in the ViewModel scope.
- It retrieves the instance of
-
build.gradle: This file specifies the dependencies required for the project. It includes dependencies for Room, ViewModel, and Kotlin.
Summary
This code showcases how to integrate Jetpack Compose with Room to manage data in an Android application. The ViewModel handles data access and updates the UI through state flows, ensuring a reactive and data-driven user experience. The code also demonstrates basic functionalities like adding, clearing, and displaying data in a list.
package com.example.composeroomexample
import android.os.Bundle
import android.util.Log
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.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import java.util.*
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { MainContent() }
}
}
@Composable
fun MainContent(){
val model = viewModel<EmployeeViewModel>()
val textState = remember{ mutableStateOf("")}
val list = model.employees.collectAsState()
Log.d("xapp", "Recomposition")
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp)
){
Button(onClick = {
val employees = mutableListOf<Employee>()
for (i in 1..10){
employees.add(
Employee(
null,UUID.randomUUID().toString()
)
)
}
model.insertAll(employees)
}) { Text(text = "Add 10 Employees") }
Button(onClick = {
val employees = mutableListOf<Employee>()
for (i in 1..10){
employees.add(
Employee(
null,UUID.randomUUID().toString()
)
)
}
model.insert(Employee(null,UUID.randomUUID().toString()))
}) { Text(text = "Add One") }
}
Row(verticalAlignment = Alignment.CenterVertically) {
TextButton(onClick = { model.clear() }) {
Text(text = "Clear")
}
Spacer(modifier = Modifier.width(12.dp))
Text(
text = if (textState.value.trim().isEmpty()){"Employees"}
else {"Employees start with (${textState.value.trim()})"} +
" ${list.value.size}",
fontWeight = FontWeight.Bold
)
}
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(vertical = 12.dp)
) {
itemsIndexed(list.value) { index,item ->
Card(
modifier = Modifier.fillMaxWidth(),
backgroundColor = Color(0xFFDCE2C9)
) {
Row(
modifier = Modifier.padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
Modifier.size(48.dp).clip(CircleShape)
.background(Color(0xFF8DB600)),
contentAlignment = Alignment.Center
) {
Text(
text = "${item.id}",
style = MaterialTheme.typography.h6
)
}
Spacer(modifier = Modifier.width(12.dp))
Text(
text = item.fullName.take(12),
style = MaterialTheme.typography.h6
)
}
}
}
}
}
}
package com.example.composeroomexample
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import android.content.Context
@Database(entities = [Employee::class], version = 1, exportSchema = false)
abstract class RoomSingleton : RoomDatabase() {
abstract fun employeeDao():EmployeeDao
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
}
}
}
package com.example.composeroomexample
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "employeeTbl")
data class Employee(
@PrimaryKey
var id:Long?,
@ColumnInfo(name = "uuid")
var fullName: String
)
package com.example.composeroomexample
import androidx.room.*
@Dao
interface EmployeeDao{
@Query("SELECT * FROM employeeTbl ORDER BY id DESC")
fun allEmployees():List<Employee>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(employee:Employee)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(list:List<Employee>)
@Query("DELETE FROM employeeTbl")
suspend fun clear()
}
package com.example.composeroomexample
import android.app.Application
import android.util.Log
import androidx.lifecycle.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class EmployeeViewModel(application:Application)
: AndroidViewModel(application){
private val db:RoomSingleton = RoomSingleton
.getInstance(application)
private val _Employees = MutableStateFlow(emptyList<Employee>())
var employees: StateFlow<List<Employee>> = _Employees
init {
loadEmployees()
}
fun loadEmployees() = effect {
_Employees.value = db.employeeDao().allEmployees()
Log.d("xapp", "Loading employees")
}
fun insert(employee: Employee){
viewModelScope.launch(Dispatchers.IO) {
db.employeeDao().insert(employee)
loadEmployees()
}
}
fun insertAll(list:List<Employee>){
viewModelScope.launch (Dispatchers.IO){
db.employeeDao().insertAll(list)
loadEmployees()
}
}
fun clear(){
viewModelScope.launch(Dispatchers.IO) {
db.employeeDao().clear()
loadEmployees()
}
}
private fun effect(block: suspend () -> Unit) {
viewModelScope.launch(Dispatchers.IO) { block() }
}
}
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'
}
- jetpack compose - Get screen width height in dp
- jetpack compose - How to hide status bar
- jetpack compose - How to hide navigation bar
- jetpack compose - Kotlinx serialization alternative json names
- jetpack compose - Kotlinx serialization build json array
- jetpack compose - How to use ViewModel state
- jetpack compose - Flow using ViewModel
- jetpack compose - Search Room data using ViewModel
- jetpack compose - ViewModel Room edit update data
- jetpack compose - ViewModel Room delete clear data
- compose glance - How to create app widget
- jetpack compose - Icon from vector resource
- jetpack compose - IconButton from vector resource