์ํ ํธ์ด์คํ - ์ํ๋ฅผ ์ด๋์ ๋ ๊ฒ์ธ๊ฐ
์ง๋ ๊ธ์์ ๋ชฉ๋ก ํ๋ฉด๊ณผ ์์ธ ํ๋ฉด์ด "๊ฐ์ ๋ฐ์ดํฐ"๋ฅผ ๊ฐ์ด ๋ด์ผ ํ๋๋ฐ, ๋ฐ์ดํฐ๊ฐ ๋ชฉ๋ก ํ๋ฉด ์์ remember๋ก ๊ฐํ ์์ด์ ์์ธ ํ๋ฉด์ด ์ ๊ทผ์ ๋ชป ํ๋ ๋ฌธ์ ๋ฅผ ๋ดค๋ค. ์ด๋ฒ์ ์ด๊ฑธ ์ ๋๋ก ํ์ด๋ณด์.
๋ฐ์ดํฐ๋ฅผ ๋ ์๋ก ์ฌ๋ฆฌ๋ฉด ๋๋ค
์ง๊ธ๊น์ง todoList๋ฅผ TodoListScreen ํจ์ ์์ ๋๋ค. ์ด๊ฑธ TodoListScreen์ ํธ์ถํ๋ ์ชฝ, ์ฆ TodoApp์ผ๋ก ์ฎ๊ฒจ๋ณด์.
@Composable
fun TodoApp() {
val todoList = remember {
mutableStateListOf(
Todo(id = 1, title = "์ฅ๋ณด๊ธฐ"),
Todo(id = 2, title = "์ด๋ํ๊ธฐ"),
Todo(id = 3, title = "์ฑ
์ฝ๊ธฐ")
)
}
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "list") {
composable("list") {
TodoListScreen(
todoList = todoList,
onAdd = { title -> todoList.add(Todo(id = todoList.size + 1, title = title)) },
onToggleDone = { id ->
val index = todoList.indexOfFirst { it.id == id }
if (index != -1) {
todoList[index] = todoList[index].copy(isDone = !todoList[index].isDone)
}
},
onDelete = { id -> todoList.removeAll { it.id == id } },
onTodoClick = { todo -> navController.navigate("detail/${todo.id}") }
)
}
composable("detail/{todoId}") { backStackEntry ->
val todoId = backStackEntry.arguments?.getString("todoId")?.toIntOrNull()
val todo = todoList.find { it.id == todoId }
TodoDetailScreen(
todo = todo,
onToggleDone = { id ->
val index = todoList.indexOfFirst { it.id == id }
if (index != -1) {
todoList[index] = todoList[index].copy(isDone = !todoList[index].isDone)
}
},
onBack = { navController.popBackStack() }
)
}
}
}todoList๊ฐ ์ด์ TodoApp์ ์์ผ๋๊น, "list" route๋ "detail/{todoId}" route๋ ๋ ๋ค ๊ฐ์ todoList๋ฅผ ๋ณด๊ณ ์๋ค. ์์ธ ํ๋ฉด์์ todoList.find { it.id == todoId }๋ก ์ฐพ์ผ๋ฉด, ๋ชฉ๋ก ํ๋ฉด์์ ์ถ๊ฐยท์ญ์ ยท์ฒดํฌํ ๋ฐ๋ก ๊ทธ ๋ฐ์ดํฐ๋ฅผ ๊ทธ๋๋ก ๊ฐ์ ธ์ฌ ์ ์๋ค.
TodoListScreen์ ๋ ์ด์ ์๊ธฐ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง ์๋๋ค
@Composable
fun TodoListScreen(
todoList: List<Todo>,
onAdd: (String) -> Unit,
onToggleDone: (Int) -> Unit,
onDelete: (Int) -> Unit,
onTodoClick: (Todo) -> Unit
) {
var inputText by remember { mutableStateOf("") }
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
TextField(
value = inputText,
onValueChange = { inputText = it },
modifier = Modifier.weight(1f),
placeholder = { Text("ํ ์ผ ์
๋ ฅ") }
)
Button(onClick = {
if (inputText.isNotBlank()) {
onAdd(inputText)
inputText = ""
}
}) {
Text("์ถ๊ฐ")
}
}
Spacer(modifier = Modifier.height(8.dp))
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(todoList, key = { it.id }) { todo ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.clickable { onTodoClick(todo) },
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = todo.isDone, onCheckedChange = { onToggleDone(todo.id) })
Text(text = todo.title, modifier = Modifier.weight(1f))
IconButton(onClick = { onDelete(todo.id) }) {
Icon(imageVector = Icons.Default.Delete, contentDescription = "์ญ์ ")
}
}
}
}
}
}[์ด๋ฏธ์ง: ๋ชฉ๋ก ํ๋ฉด์์ ์ฒดํฌํ๊ณ , ์์ธ ํ๋ฉด์ผ๋ก ๋ค์ด๊ฐ๋ ๊ฐ์ ํญ๋ชฉ์ด ์ฒดํฌ๋ ์ฑ๋ก ๋ณด์ด๋ ํ๋ฉด]
๋์ฌ๊ฒจ๋ณผ ๋ถ๋ถ์ด ์๋ค. ์ฒดํฌ๋ฐ์ค๊ฐ var isChecked by remember { ... }๋ฅผ ์ฐ๋ ๊ฑธ ๊ฑท์ด๋ด๊ณ , checked = todo.isDone์ผ๋ก ๋ฐ๋์๋ค. ์ฒดํฌ ์ฌ๋ถ๋ฅผ Checkbox๊ฐ ์ง์ ๊ธฐ์ตํ๋ ๊ฒ ์๋๋ผ, todoList์ ์๋ Todo.isDone ๊ฐ์ ๊ทธ๋๋ก ๋ณด์ฌ์ฃผ๊ธฐ๋ง ํ๋ค. ์ฒดํฌ๋ฐ์ค๋ฅผ ๋๋ฅด๋ฉด onToggleDone(todo.id)๋ฅผ ํธ์ถํด์ "๋ฐ๊ฟ๋ฌ๋ผ"๊ณ ์๋ก ์์ฒญ๋ง ํ๊ณ , ์ค์ ๋ก ๊ฐ์ ๋ฐ๊พธ๋ ๊ฑด TodoApp์ด ํ๋ค.
์ด ํจํด์ ์ํ ํธ์ด์คํ
(State Hoisting)์ด๋ผ๊ณ ๋ถ๋ฅธ๋ค. ์ปดํฌ์ ๋ธ์ด ์ํ๋ฅผ ์ง์ ๊ฐ์ง ์๊ณ , ์ํ๊ฐ๊ณผ "๋ฐ๊ฟ๋ฌ๋ผ"๋ ์ฝ๋ฐฑ์ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์์ ๊ทธ๋๋ก ๋ณด์ฌ์ฃผ๊ธฐ๋ง ํ๋ ๊ฑฐ๋ค. ์ฌ์ค TextField๋ ๋๊ฐ์ ๊ตฌ์กฐ๋ค: value๋ฅผ ๋ฐ์์ ๋ณด์ฌ์ฃผ๊ณ , ์ฌ์ฉ์๊ฐ ํ์ดํํ๋ฉด ๊ทธ ๊ฐ์ ์ง์ ๋ฐ๊พธ๋ ๊ฒ ์๋๋ผ onValueChange๋ก "์ด๋ ๊ฒ ๋ฐ๊ฟ๋ฌ๋ผ"๊ณ ์๋ ค์ฃผ๊ธฐ๋ง ํ๋ค.
์์ธ ํ๋ฉด๋ ์ด์ ์ง์ง ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ค ์ ์๋ค
@Composable
fun TodoDetailScreen(
todo: Todo?,
onToggleDone: (Int) -> Unit,
onBack: () -> Unit
) {
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
if (todo == null) {
Text("ํด๋น ํ ์ผ์ ์ฐพ์ ์ ์๋ค")
} else {
Text(text = todo.title, style = MaterialTheme.typography.titleLarge)
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = todo.isDone, onCheckedChange = { onToggleDone(todo.id) })
Text(if (todo.isDone) "์๋ฃ" else "๋ฏธ์๋ฃ")
}
}
Button(onClick = onBack) {
Text("๋ค๋ก ๊ฐ๊ธฐ")
}
}
}todo๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๊ทธ๋๋ก ๋ฐ์ผ๋๊น, ๋ชฉ๋ก ํ๋ฉด์์ ์
๋ ฅํ ์ ๋ชฉ๊ณผ ์ฒดํฌ ์ํ๊ฐ ์์ธ ํ๋ฉด์๋ ๊ทธ๋๋ก ๋ณด์ธ๋ค. ์์ธ ํ๋ฉด์์ ์ฒดํฌ๋ฐ์ค๋ฅผ ๋๋ฌ๋ onToggleDone์ ํตํด TodoApp์ todoList๊ฐ ๋ฐ๋๋๊น, ๋ค๋ก ๊ฐ๊ธฐ๋ฅผ ๋๋ฅด๋ฉด ๋ชฉ๋ก ํ๋ฉด์๋ ๋ฐ๋ ์ํ๊ฐ ๋ฐ์๋ผ ์๋ค.
์ ๊ตณ์ด ์ด๋ ๊ฒ ๊ท์ฐฎ๊ฒ ๋ง๋๋?
TodoListScreen์ด๋ TodoDetailScreen์ด ์ง์ todoList๋ฅผ ๊ฐ๊ณ ์์๋ค๋ฉด ๋ ์งง๊ฒ ์ธ ์ ์์์ ๊ฑฐ๋ค. ๊ทผ๋ฐ ๊ทธ๋ฌ๋ฉด ์ง๋ ๊ธ์์ ๋ณธ ๊ฒ์ฒ๋ผ, ๊ทธ ๋ฐ์ดํฐ๋ ๊ทธ ์ปดํฌ์ ๋ธ ์์๋ง ๊ฐํ์ ๋ค๋ฅธ ํ๋ฉด์ด ๋ณผ ์ ์๋ค. ์ํ๋ฅผ ์๋ก ์ฌ๋ ค์ "์ฌ๋ฌ ๊ณณ์์ ๊ฐ์ด ๋ด์ผ ํ๋ ๋ฐ์ดํฐ๋ ๊ทธ๊ฑธ ๊ฐ์ด ๋ณด๋ ํ๋ฉด๋ค์ ๊ณตํต ์กฐ์์ ๋๋ค"๋ ์์น์ ์งํค๋ฉด, ํ๋ฉด์ด ๋ช ๊ฐ๋ก ๋์ด๋๋ ๋ฐ์ดํฐ๋ ํ๋๋ง ์กด์ฌํ๊ฒ ๋๋ค.
์ ๋ฆฌ
- ์ฌ๋ฌ ํ๋ฉด์ด ๊ฐ์ด ๋ด์ผ ํ๋ ๋ฐ์ดํฐ๋, ๊ทธ ํ๋ฉด๋ค์ ๊ฐ์ธ๋ ์์ ์ปดํฌ์ ๋ธ๋ก ๋์ด์ฌ๋ฆฐ๋ค (์ํ ํธ์ด์คํ )
- ์ํ๋ฅผ ํธ์ด์คํ
ํ ์ปดํฌ์ ๋ธ์
value์onValueChange๊ฐ์ ์ฝ๋ฐฑ๋ง ๋ฐ๋ "๊ป๋ฐ๊ธฐ"๊ฐ ๋๋ค TextField๊ฐ ์ฐ๋value+onValueChange๊ตฌ์กฐ๊ฐ ๋ฐ๋ก ์ด ํจํด์ด๋ค
๊ทผ๋ฐ ์ด๊ฒ๋ ํ๊ณ๊ฐ ์๋ค
todoList๊ฐ ์ด์ TodoApp์ ์์ง๋ง, ์ฌ์ ํ ์ปดํฌ์ ๋ธ ํจ์ ์์ remember๋ค. ํ๋ฉด์ ํ์ ํ๋ฉด TodoApp๋ ๋ค์ ์คํ๋๋๋ฐ, mutableStateListOf๋ก ๋ง๋ ๋ฆฌ์คํธ๋ rememberSaveable๋ก ๋ฐ๋ก ์ ์ฅํ ์ ์๋ ํํ๊ฐ ์๋๋ผ์, ์ง๊ธ ์ฝ๋ ๊ทธ๋๋ก๋ ํ๋ฉด ํ์ ์ ๋ฐ์ดํฐ๊ฐ ๋ ์๊ฐ๋ค. ๊ทธ๋ฆฌ๊ณ "ํ ์ผ์ ์ถ๊ฐ/์ญ์ /ํ ๊ธํ๋ ๋ก์ง"์ด ์ ๋ถ UI๋ฅผ ๊ทธ๋ฆฌ๋ ์ปดํฌ์ ๋ธ ํจ์ ์์ ๋ค์์ฌ ์์ด์, ํ๋ฉด๊ณผ ๋ก์ง์ ๋ถ๋ฆฌํ๊ธฐ๋ ์ด๋ ต๋ค.
๋ค์ ๊ธ์์๋ ์ด ๋ฐ์ดํฐ์ ๋ก์ง์ ์ปดํฌ์ ๋ธ ๋ฐ, ViewModel๋ก ์ฎ๊ฒจ์ ์ด ๋ ๋ฌธ์ ๋ฅผ ๊ฐ์ด ํด๊ฒฐํด๋ณด๊ฒ ๋ค.
