안드로이드 코틀린
[Kotlin][Android] RoomDB + Flow + LazyColum 으로 Todo리스트 만들기
teamnova
2025. 8. 8. 23:47
728x90
안녕하세요.
Room DB에 저장된 할 일 목록(Todo)을 Flow로 받아와서, Jetpack Compose의 LazyColumn으로 실시간 변화(추가/삭제/수정 등)가 즉시 반영되는 화면을 만드는 예제 코드를 만들어보겠습니다.
1. 프로젝트 생성 및 의존성 추가
1) 새 프로젝트 생성
- 언어: Kotlin
- Minimum SDK: 23 이상
- 템플릿: Empty Compose Activity
2) 의존성 추가
Project 수준 build.gradle.kts 에 KSP를 추가합니다.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
//...
id("com.google.devtools.ksp") version "2.0.21-1.0.27" apply false
}
App 수준 build.gradle.kts 에 RoomDB, ViewModel 의존성을 추가합니다.
plugins {
//...
id("com.google.devtools.ksp")
}
dependencies {
//...
// Room
val room_version = "2.7.2"
implementation ("androidx.room:room-runtime:$room_version")
ksp ("androidx.room:room-compiler:$room_version")
implementation ("androidx.room:room-ktx:$room_version")
// ViewModel
val view_model_version = "2.9.2"
implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:$view_model_version")
implementation ("androidx.lifecycle:lifecycle-viewmodel-ktx:$view_model_version")
}
2. RoomDB 구성
1) TodoEntity 만들기
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "todos")
data class TodoEntity(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val content: String,
val isDone: Boolean = false,
val createdAt: Long = System.currentTimeMillis()
)
2) TodoDao 만들기
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
@Dao
interface TodoDao {
@Query("SELECT * FROM todos ORDER BY id DESC")
fun getAllTodos(): Flow<List<TodoEntity>>
@Insert
suspend fun insert(todo: TodoEntity)
@Update
suspend fun update(todo: TodoEntity)
@Delete
suspend fun delete(todo: TodoEntity)
}
3) Room Database 클래스 만들기
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(entities = [TodoEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun todoDao(): TodoDao
}
3. ViewModel 에서 데이터 관리
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import androidx.room.Room
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
class TodoViewModel(application: Application) : AndroidViewModel(application) {
private val db = Room.databaseBuilder(
application,
AppDatabase::class.java,
"todo-db"
).build()
private val todoDao = db.todoDao()
// Room의 Flow를 Compose에서 관찰할 수 있도록 StateFlow로 변환
val todos: StateFlow<List<TodoEntity>> = todoDao.getAllTodos()
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
// 할 일 추가
fun addTodo(content: String) {
viewModelScope.launch {
todoDao.insert(TodoEntity(content = content))
}
}
// 할 일 완료 토글
fun toggleDone(todo: TodoEntity) {
viewModelScope.launch {
todoDao.update(todo.copy(isDone = !todo.isDone))
}
}
// 할 일 삭제
fun deleteTodo(todo: TodoEntity) {
viewModelScope.launch {
todoDao.delete(todo)
}
}
}
4. Jetpack Compose로 UI 만들기
1) 할 일 목록 화면 만들기
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun TodoScreen(viewModel: TodoViewModel = viewModel()) {
val todos by viewModel.todos.collectAsState()
var newTodo by remember { mutableStateOf("") }
Column(modifier = Modifier.padding(16.dp)) {
Row {
TextField(
value = newTodo,
onValueChange = { newTodo = it },
label = { Text("할 일 입력") },
modifier = Modifier.weight(1f)
)
Button(
onClick = {
if (newTodo.isNotBlank()) {
viewModel.addTodo(newTodo)
newTodo = ""
}
},
modifier = Modifier.padding(start = 8.dp)
) {
Text("추가")
}
}
Spacer(modifier = Modifier.height(16.dp))
LazyColumn {
items(todos.size, key = { todos[it].id }) { index ->
val todo = todos[index]
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
) {
Checkbox(
checked = todo.isDone,
onCheckedChange = { viewModel.toggleDone(todo) }
)
Text(
text = todo.content,
modifier = Modifier.weight(1f),
style = if (todo.isDone) TextStyle(textDecoration = TextDecoration.LineThrough) else LocalTextStyle.current
)
IconButton(onClick = { viewModel.deleteTodo(todo) }) {
Icon(Icons.Default.Delete, contentDescription = "삭제")
}
}
HorizontalDivider(Modifier.padding(horizontal = 16.dp))
}
}
}
}
2) MainActivity에서 화면 연결
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TodoScreen()
}
}
}