This code demonstrates a Jetpack Compose application that allows users to search for employees stored in a Room database. It utilizes ViewModel to manage the application's data and state.
Data Model and Room Integration:
- The
Employee
class represents an employee record withid
(primary key) andfullName
attributes. RoomSingleton
is a Room database class that provides access to the database instance and theEmployeeDao
interface.EmployeeDao
defines methods for interacting with the employee table: search, insert, insert all, and clear.
EmployeeViewModel:
EmployeeViewModel
inherits fromAndroidViewModel
and manages the application's data lifecycle.- It holds a private
_searchedEmployees
mutable state flow to store the list of searched employees. - A public read-only
searchedEmployees
state flow exposes the search results to the UI. - The
loadSearchedEmployees
function retrieves employees from the database based on the search query and updates the_searchedEmployees
state flow. - Other functions like
insert
,insertAll
, andclear
handle adding and removing employees from the database, triggering a search update afterward.
MainActivity with Jetpack Compose UI:
- The
MainContent
composable function builds the UI for the application. - It utilizes ViewModel composition to access the
EmployeeViewModel
. - A text field allows users to enter a search term.
- Buttons are provided to add 100 random employees or clear all employees from the database.
- The UI displays the number of employees and a list of searched employees using a
LazyColumn
.
Summary:
This code showcases a well-structured implementation for searching Room data using ViewModel in a Jetpack Compose application. It demonstrates proper separation of concerns between the UI, data access layer, and data management logic. The use of state flows ensures a reactive and efficient UI update based on changes in the 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.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.searchedEmployees.collectAsState()
Log.d("xapp", "Recomposition")
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp)
){
// add new employees
Button(onClick = {
val employees = mutableListOf<Employee>()
for (i in 1..100){
employees.add(
Employee(
null,UUID.randomUUID().toString()
)
)
}
model.insertAll(employees)
}) { Text(text = "Add 100 Employees") }
// delete all employees
Button(onClick = { model.clear() }) {
Text(text = "Clear")
}
}
Text(
text = if (textState.value.trim().isEmpty()){"Employees"}
else {"Employees start with (${textState.value.trim()})"} +
" ${list.value.size}",
fontWeight = FontWeight.Bold
)
Row() {
TextField(
value = textState.value,
onValueChange = {
textState.value = it
model.loadSearchedEmployees(it.trim())
},
modifier = Modifier.fillMaxWidth(),
placeholder = { Text(text = "Search employee")}
)
}
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
)
}
}
}
}
}
}
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 WHERE uuid" +
" LIKE:startsWith || '%' ORDER BY id DESC")
fun searchEmployee(startsWith:String):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()
}
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 _searchedEmployees = MutableStateFlow(emptyList<Employee>())
var searchedEmployees: StateFlow<List<Employee>> = _searchedEmployees
init {
loadSearchedEmployees("")
}
fun loadSearchedEmployees(startsWith: String) = effect {
_searchedEmployees.value = db.employeeDao().searchEmployee(startsWith)
Log.d("xapp", "Loading employees")
}
fun insert(employee: Employee){
viewModelScope.launch(Dispatchers.IO) {
db.employeeDao().insert(employee)
loadSearchedEmployees("")
}
}
fun insertAll(list:List<Employee>){
viewModelScope.launch (Dispatchers.IO){
db.employeeDao().insertAll(list)
loadSearchedEmployees("")
}
}
fun clear(){
viewModelScope.launch(Dispatchers.IO) {
db.employeeDao().clear()
loadSearchedEmployees("")
}
}
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'
}
- jetpack compose - Kotlinx serialization build json array
- jetpack compose - Flow current time
- jetpack compose - How to flow a list
- jetpack compose - How to use ViewModel state
- jetpack compose - Flow using ViewModel
- jetpack compose - ViewModel Room add insert data
- 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