Jetpack compose: ViewModel Room edit update data

Introduction

This code demonstrates an Android application built with Jetpack Compose and Room Persistence Library. The application manages employee data, allowing users to add, update, and clear employee records. The code utilizes several key components:

  • Jetpack Compose: A modern UI framework for building declarative and reactive user interfaces.
  • Room: A persistence library that provides an abstraction layer for SQLite databases.
  • ViewModel: A lifecycle-aware class that manages data for a UI component.

Breakdown of the Code

The code is divided into several files:

  • MainActivity.kt: The main activity of the application. It sets up the content of the screen using setContent and utilizes EmployeeViewModel to manage employee data.
  • RoomSingleton.kt: Defines a singleton class for accessing the Room database instance.
  • RoomEntity.kt: Defines the data structure for an employee (Employee) with fields for ID and full name.
  • RoomDao.kt: Defines the Data Access Object (DAO) interface for interacting with the employee table in the database.
  • EmployeeViewModel.kt: The ViewModel class responsible for managing employee data. It interacts with the DAO to perform database operations and exposes functions to add, update, clear, and load employees.
  • build.gradle: Defines the necessary dependencies for the project, including Room and ViewModel libraries.

Summary

The code showcases a practical example of using Jetpack Compose with Room for data persistence. The ViewModel acts as a central point for data management, ensuring data consistency and providing a reactive way to update the UI based on changes in the database. The application demonstrates basic CRUD (Create, Read, Update, Delete) operations for employee data.


MainActivity.kt

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.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
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 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 = {
                model.clear()
            }) { Text(text = "Clear") }
        }

        Text(
            text ="${list.value.size} Employees",
            fontWeight = FontWeight.Bold
        )

        LazyColumn(
            verticalArrangement = Arrangement.spacedBy(8.dp),
        ) {
            itemsIndexed(list.value) { index,item ->
                Card(
                    modifier = Modifier.fillMaxWidth(),
                    backgroundColor = Color(0xFFF1E9D2)
                ) {
                    Row(
                        modifier = Modifier.padding(12.dp),
                        verticalAlignment = Alignment.CenterVertically,
                        horizontalArrangement = Arrangement.spacedBy(12.dp)
                    ) {
                        Box(
                            Modifier.size(48.dp).clip(CircleShape)
                                .background(Color(0xFFF6ADC6)),
                            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
                        )

                        Spacer(modifier = Modifier.weight(1F))
                        IconButton(onClick = {
                            val updatedItem = Employee(
                                item.id,UUID.randomUUID().toString()
                            )
                            model.update(updatedItem)
                        }) {
                            Icon(
                                imageVector = Icons.Default.Edit,
                                contentDescription = ""
                            )
                        }
                    }
                }
            }
        }
    }
}
RoomSingleton.kt

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
        }
    }
}
RoomEntity.kt

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
)
RoomDao.kt

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

    @Update(onConflict = OnConflictStrategy.REPLACE)
    suspend fun update(employee:Employee)

    @Query("DELETE FROM employeeTbl")
    suspend fun clear()
}
EmployeeViewModel.kt

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 update(employee: Employee){
        viewModelScope.launch(Dispatchers.IO){
            db.employeeDao().update(employee)
            loadEmployees()
        }
    }

    fun clear(){
        viewModelScope.launch(Dispatchers.IO) {
            db.employeeDao().clear()
            loadEmployees()
        }
    }

    private fun effect(block: suspend () -> Unit) {
        viewModelScope.launch(Dispatchers.IO) { block() }
    }
}
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'
}
More android jetpack compose tutorials