Jetpack compose: ViewModel Room delete clear data

Introduction

This code demonstrates a Jetpack Compose application that utilizes Room database to manage employee data. The application allows adding new employees, deleting existing ones, and clearing all data. It leverages ViewModel to manage the data state and provide a single source of truth for the UI.

Breakdown

The code is organized into several classes:

  • MainActivity.kt: This is the main activity that sets the content of the app using a Composable function called MainContent.
  • MainContent.kt: This composable function builds the UI for the app. It retrieves data from the ViewModel, displays the number of employees, and presents a list of employees. It also provides buttons for adding new employees and clearing all data.
  • RoomSingleton.kt: This class defines a Room database named "roomdb" with a single entity - Employee. It provides a singleton instance using the getInstance method.
  • Employee.kt: This data class represents an employee with an ID (Long) and a full name (String).
  • EmployeeDao.kt: This interface defines the data access methods for the Employee table in the Room database. It includes methods for fetching all employees, inserting a single or multiple employees, updating an employee, deleting an employee, and clearing all data.
  • EmployeeViewModel.kt: This ViewModel class manages the employee data. It retrieves data from the database on initialization and provides functions for adding, updating, deleting, and clearing employees. It also exposes a StateFlow of employees to be observed by the UI.
  • build.gradle: This file specifies the dependencies required for the project, including Room and ViewModel libraries.

Summary

This code showcases a well-structured Jetpack Compose application with Room database integration. The ViewModel effectively manages the data state and interacts with the database through the defined Dao methods. The UI is built using Compose functions and reacts to changes in the data flow. Overall, the code demonstrates good practices for data management and UI updates in a Jetpack Compose application.


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.Delete
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(0xFFC0E8D5)
                ) {
                    Row(
                        modifier = Modifier.padding(12.dp),
                        verticalAlignment = Alignment.CenterVertically,
                        horizontalArrangement = Arrangement.spacedBy(12.dp)
                    ) {
                        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
                        )

                        Spacer(modifier = Modifier.weight(1F))
                        IconButton(onClick = {
                            model.delete(item)
                        }) {
                            Icon(
                                imageVector = Icons.Default.Delete,
                                contentDescription = "",
                                tint = Color(0xFFD3212D)
                            )
                        }
                    }
                }
            }
        }
    }
}
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)

    @Delete
    suspend fun delete(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()
    }

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