간단한 투두앱을 만들어 보자 ===== 간단한 데이터 만들기 ===== 우리는 data class를 이용하여 Todo 데이터의 구조를 맏들 것이다. 그리고 이렇게 만든 데이터 클래스에 데이터를 다음과 같이 집어 넣어 보자 package com.dklaw.gogo2.database import java.time.LocalDate data class Todo( var id : Int, var title : String, var createdAt : LocalDateTime ) fun getFakeTodo() : List { return listOf( Todo(1, "First todo", LocalDateTime.now()), Todo(2, "Seconde todo", LocalDateTime.now()), Todo(3, "Third todo", LocalDateTime.now()), Todo(4, "This is the last todo", LocalDateTime.now()) ) } ===== 화면에 출력하기 ===== ==== 1. 간단하게 출력해보기 ===== 위의 제목과 만든 날짜를 화면에 간단하게 출력해보자 package com.dklaw.gogo2.pages import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.dklaw.gogo2.database.Todo import com.dklaw.gogo2.database.getFakeTodo import java.time.format.DateTimeFormatter @Composable fun ContactsPage() { val todoList = getFakeTodo() Column(modifier = Modifier .fillMaxHeight() .padding(8.dp)) { LazyColumn( content = { itemsIndexed(todoList) { index : Int, item : Todo -> TodoItem(item = item) } } ) } } @Composable fun TodoItem(item : Todo) { val timeFormater : DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy년 M월 d일 a h시 m분") val nowString = item.createdAt.format(timeFormater) Row { Column { Text(text = nowString) Text(text = item.title) } } } ===== 데이터 조작하기 ===== ==== 1. Todo Manager ==== package com.dklaw.gogo2.database import java.time.LocalDateTime object TodoManager { private val todoList = mutableListOf() fun getAllTodo() : List{ return todoList } fun addTodo(title : String) { todoList.add(Todo(System.currentTimeMillis().toInt(), title, LocalDateTime.now())) } fun deleteTodo(id : Int) { todoList.removeIf{ it.id == id } } } ==== 2. view 모델 ==== Todo Manager에서 한 데이터 작업을 뷰모델에서 다시 가져온다. package com.dklaw.gogo2.database import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel class TodoViewModel: ViewModel() { private var _todoList = MutableLiveData>() val todoList : LiveData> = _todoList fun getAllTodo() { _todoList.value = TodoManager.getAllTodo() } fun addTodo(title : String) { TodoManager.addTodo(title) getAllTodo() } fun deleteTodo(id : Int) { TodoManager.deleteTodo(id) getAllTodo() } } ===== 뷰모델 동기화하기 ===== ==== 1. 뷰모델을 상속하기 ==== TodoListPage.kt 에서는 다음과 같이 뷰모델을 상속하여 뷰모델과 동기화할 준비를 한다. @Composable fun ContactsPage(viewModel: TodoViewModel) { val todoList by viewModel.todoList.observeAsState() var inputText by remember { mutableStateOf("") } observeAsState()함수를 사용하려면 livedata 디펜던시를 build.gradle.kts(module :app)에 추가해야 한다. > implementation("androidx.compose.runtime:runtime-livedata:1.6.8") ==== 2. 뷰모델 공급자 지정 ==== 다음과 같이 mainactivity에 공급자를 지정한다. class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val todoViewModel = ViewModelProvider(this)[TodoViewModel::class.java] enableEdgeToEdge() setContent { ==== 3. 데이터가 없는 경우에 대한 예외 처리 ==== TodoListPage.kt는 다음과 같이 수정한다. fakeTodoList()가 아닌 실시간 Todo ViewModel을 사용하는 것이므로 처음에는 데이터 값이 없다. 따라서 이에 대한 예외를 처리해 줘야 한다. todoList?.let { LazyColumn( content = { itemsIndexed(it) { index : Int, item : Todo -> TodoItem(item = item) } } ) }?: Text( modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center, text = "No Items yet", fontSize = 16.sp ) ==== 4. 버튼 클릭 이벤트 ==== === 가. 추가하기 버튼 ==== TodoListPage.kt에 다음과 같이 버튼 클릭 이벤트를 추가한다. OutlinedTextField(value = inputText, onValueChange = { inputText = it }) Button(onClick = { viewModel.addTodo(inputText); inputText = ""; }) { Text(text = "Add") } === 나. 삭제하기 버튼 === TodoListPage.kt에 다음과 같이 버튼 클릭 이벤트를 추가한다. TodoItem(item = item, onDelete = {viewModel.deleteTodo(item.id)}) 위와 같이 onDelete라는 대리자를 지정했으므로 다음과 같이 TodoItem함수를 바꿔준다. fun TodoItem(item : Todo, onDelete : ()-> Unit) { (중략) IconButton(onClick = onDelete ) { (후략) ==== 다. 전체 코드 ==== TodoListPage.kt의 전체 코드는 다음과 같다. package com.dklaw.gogo2.pages import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight 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.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState 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.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.dklaw.gogo2.R import com.dklaw.gogo2.database.Todo import com.dklaw.gogo2.database.TodoViewModel import java.time.format.DateTimeFormatter @Composable fun ContactsPage(viewModel: TodoViewModel) { val todoList by viewModel.todoList.observeAsState() var inputText by remember { mutableStateOf("") } Column(modifier = Modifier .fillMaxHeight() .padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally) { Spacer(modifier = Modifier.height(32.dp)) Text(text = "할일", fontSize = 32.sp, modifier = Modifier.padding(vertical = 16.dp)) Row (modifier = Modifier .fillMaxWidth() .padding(8.dp), horizontalArrangement = Arrangement.SpaceEvenly){ OutlinedTextField(value = inputText, onValueChange = { inputText = it }) Button(onClick = { viewModel.addTodo(inputText) }) { Text(text = "Add") inputText = "" } } todoList?.let { LazyColumn( content = { itemsIndexed(it) { index : Int, item : Todo -> TodoItem(item = item, onDelete = {viewModel.deleteTodo(item.id)}) } } ) }?: Text( modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center, text = "No Items yet", fontSize = 16.sp ) } } @Composable fun TodoItem(item : Todo, onDelete : ()-> Unit) { val timeFormater : DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy년 M월 d일 a h시 m분") val nowString = item.createdAt.format(timeFormater) Row (modifier = Modifier .fillMaxWidth() .padding(8.dp) .clip(RoundedCornerShape(16.dp)) .background(MaterialTheme.colorScheme.primary) .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { Column (modifier = Modifier.weight(1f)){ Text(text = nowString, fontSize = 10.sp, color = Color.LightGray) Text(text = item.title, fontSize = 20.sp, color = Color.White) } IconButton(onClick = onDelete ) { Icon(painter = painterResource(id = R.drawable.baseline_delete_24), contentDescription = "Delete", tint = Color.White) } } }