간단한 투두앱을 만들어 보자
===== 간단한 데이터 만들기 =====
우리는 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)
}
}
}