Jetpack Compose๋ฅผ ์ด์ฉํด ๊ฐ๋จํ To-do ์ฑ์ ๋ง๋ค๋ฉฐ ๋๋ ์ ๊ณผ ์ฅ๋จ์ ์ ๋ํด ๋ค๋ฃน๋๋ค.
๐ ๋ ๋๊ตฐ๋ฐ
๊ณ 3 ์
์๋ฅผ ์ค๋นํ๋๋ผ ์๋๋ก์ด๋ ๊ฐ๋ฐ์ 1๋
์ ๋ ์ฌ์๋ค. ๊ทธ๋์ ์์๋ ๊ตต์งํ ๋ณํ๋ค์ ์ดํด๋ณด๋, ์ต๊ทผ ํ์ฑ๊ฐ์ด ๋ฑ์ฅํ Jetpack Compose๊ฐ ์์ฒญ ์ ํ์ด๋ผ๊ณ ํ๋ค. ๋์ฒด ์ปดํฌ์ฆ๊ฐ ๋ญ๊ณ , ์ผ๋ง๋ ๋๋จํ๊ธธ๋ xml์ ๋์ฒดํ๋ค๋ ์๊ธฐ๊น์ง ๋์ค๋๊ฑธ๊น?

์๋ ์๋๋ก์ด๋ ๊ฐ๋ฐ์์๋ ์ฑ์ ๋ ์ด์์(UI)์ ํํํ๊ธฐ ์ํด xml์ ์ฌ์ฉํด์๋ค. xml๋ก ๋ ์ด์์์ ๋จผ์ ์์ฑํ ๋ค, ์๋ฐ/์ฝํ๋ฆฐ ์ฝ๋์์ ๋ ์ด์์์ ๊ตฌ์ฑ ์์(View)๋ค์ ํธ์ถํด์ ์ํตํ๋ ๋ฐฉ์์ด๋ค. ํ์ง๋ง ์ด๋ฌํ ๋ฐฉ์์๋ ๋ช๋ช ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ ์ ์ด ์กด์ฌํ๋ค.
- Java/Kotlin ์ฝ๋์ ๋์ ์์กด์ฑ์ ๊ฐ์ง
์ด๋ฏธ ์์ฑ๋ ์ฑ์์ ๋ฒํผ์ ํ๋ ๋นผ๊ณ ์ถ๋ค๊ณ ํ์. ๋จ์ํ xml์ ์์ ํด์ ๋ฒํผ์ ์ง์ฐ๋ฉด ๋ ๊ฒ ๊ฐ์ง๋ง, ํด๋น ๋ฒํผ๊ณผ ์ฐ๊ฒฐ๋ Java/Kotlin ์ฝ๋๋ฅผ ํจ๊ป ์ญ์ ํ์ง ์์ผ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
- ํ๋ฉด ์์์ ์ฌ์ฌ์ฉ์ฑ์ด ๋ฎ์
5๊ฐ์ ์ ๋ ฅ ์นธ์ด ์๋ ํ์๊ฐ์ ํ๋ฉด์ ๋ง๋ค๊ณ ์ ํ๋ค. ์ ๋ ฅ ์นธ์ด ๋ชจ๋ ๋๊ฐ์ด ์๊ฒผ๋๋ผ๋, 5๊ฐ์ EditText๋ฅผ ํ๋ํ๋ ๋ง๋ค์ด ์ฃผ์ด์ผ ํ๋ค.
- ์ฝ๋๊ฐ ๊ธธ๊ณ ๋ณด์ผ๋ฌํ๋ ์ดํธ๊ฐ ๋ง์
xml ์ธ์ด ์์ฒด์ ํน์ฑ๊ณผ ์์ ํ ์ฌ์ฌ์ฉ์ฑ ๋ฌธ์ ๋ก ์ธํด, ๋ ์ด์์์ด ๋ณต์กํ ์๋ก ์ฝ๋๊ฐ ๋น๋ํด์ง๊ณ ์์๋ณด๊ธฐ ์ด๋ ค์์ง๋ค.
๋ ๊ธฐ์กด์ View ์์คํ ์ ์๊ฐ์ด ํ๋ฅผ์๋ก ์์๊ณผ ๋ ๊ฑฐ์ ๋ฉ์ด๋ฆฌ๊ฐ ๋์ด๋ฒ๋ ค์, ์ ์ง๋ณด์ํ๋ ์ ์ฅ์์๋ ๊ณ ๋ฏผ์ด ์ด๋ง์ ๋ง์ด ์๋์์ ๊ฒ์ด๋ค. (์ํฐํด ์์ฑ์ผ ๊ธฐ์ค View.java์ ์ฝ๋ ๊ธธ์ด๋ 30409์ค์ด๋ค.)


Jetpack Compose๋ ๋ค์ดํฐ๋ธ UI๋ฅผ ๋น๋ํ๊ธฐ ์ํ Android์ ์ต์ ๋๊ตฌ ํคํธ์ ๋๋ค.
๋ฐ๋ผ์ ์ด๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด Kotlin๊ณผ ์ ์ธํ UI๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ง๋ค์ด์ง ๊ฒ์ด Jetpack Compose๋ค. ์ปดํฌ์ฆ์ ๊ตฌ์ฑ ์์๋ค์ ๋ชจ๋ Kotlin ํจ์๋ก ๋ง๋ค์ด์ ธ ์๋ค. ๋ฐ๋ผ์ ์ฌ์ฌ์ฉ์ด ์ฝ๊ณ ์ฝ๋๊ฐ ๊ฐ๊ฒฐํ๋ค๋ ์ฅ์ ์ด ์๋ค. ๋ ์ด์์์ ์์ฑํ๋ ค๊ณ ์ฌ๋ฌ ์ธ์ด๋ฅผ ๋ฐฐ์ธ ํ์๋ ์๊ณ , ๊ตฌ๊ธ ์ ์ง๊ด์ ์ด๊ธฐ๊น์ง ํ๋ค. ์์ฒญ ์ข์๋ณด์ธ๋ค. ๊ทผ๋ฐ ์ด๊ฑฐ ์ด๋์ ๋ณธ ๊ฒ ๊ฐ์๋ฐ...

ํ๋ฌํฐ๋ ๋ฌธ๋ฒ์ , ๊ตฌ์กฐ์ ์ผ๋ก ๋น์ทํ ๋ถ๋ถ์ด ๋ง๋ค. ๊ฐ์ ๊ตฌ๊ธ์์ ๋ง๋ค๊ธฐ๋ ํ๊ณ , ๋ ๋ค ๊ธฐ๋ณธ์ ์ผ๋ก ๋จธํฐ๋ฆฌ์ผ ๋์์ธ์ ๊ธฐ๋ฐ์ผ๋ก ํ๊ณ ์์ด์ ๊ทธ๋ฐ ๊ฒ ๊ฐ๋ค. ๊ทผ๋ฐ ๋์ค์ ํ๋ฌํฐ๊ฐ ์ถฉ๋ถํ stable ํด์ง๋ฉด ์ปดํฌ์ฆ๋ 'iOS ์๋๋ ๋ฐ์ชฝ์ง๋ฆฌ ํ๋ฌํฐ'๊ฐ ๋๋ ๊ฒ ์๋๊น? (์ด๋ฏธ์ง: Reddit)
๐ค ์ผ๋จ ์จ๋ณด์

์ผ๋จ ํ๋ฒ ์จ๋ณด๊ธฐ๋ก ํ๋ค. ์ฐ์ Compose ๊ธฐ์ด ์ฝ๋๋ฉ์ ์ฐพ์์ ์๊ฐํ๋ค. ํด๋น ์ฝ๋๋ฉ์์๋ ์ปดํฌ์ฆ์ ๊ธฐ์ด์ ์ธ ๋์ ์๋ฆฌ์ UI ์์ฑ ๋ฐฉ๋ฒ, State์ LazyColumn(RecyclerView ๋์ฒด)์ ๋ํด ๋ค๋ฃจ๊ณ ์๋ค. ์ ์ด๋ฏธ์ง์ฒ๋ผ ํผ์ณ์ง๋ ๋ฆฌ์คํธ๋ฅผ ๋ง๋๋ ๊ฒ์ด ์ต์ข ๋ชฉํ๋ค.
์ผ๋จ ์๋๋ก์ด๋ ์ง์ ์ต๋ ๋น๋ฐ์ธ RecyclerView๋ฅผ ์ ์จ๋ ๋๋๊ฒ ๋๋ฌด ์ข์๋ค. ๋ฆฌ์คํธ ํ๋ ๋์ฐ๋๋ฐ ViewGroup, LayoutManager, ViewHolder, Adapter, DiffUtil, Item layout... ์ด๊ฒ ๋ค ํ์ํ๋ค๋๊ฒ ๋ง์ด ๋๋? ์ปดํฌ์ฆ์์๋ LazyColumn์ ๋ฐ์ดํฐ๋ง ๋ฃ์ผ๋ฉด ์์์ ๊ทธ๋ ค์ค๋ค.
์ปดํฌ์ฆ์ ์ ์ธํ UI๋ ์ฒ์์๋ ๋ํดํ๋ฐ ์ฐ๋ค ๋ณด๋ฉด ์กฐ๊ธ์ฉ ์ ์๋๋ ๋๋์ด๋ค. ํ๋ฉด ๊ตฌ์ฑ ์์๋ค์ ํจ์๋ก ์๊ฒ ์ชผ๊ฐค ์ ์๊ธฐ ๋๋ฌธ์ ์ค์ฒฉ๋ ๋ ์ด์์์ ๊ด๋ฆฌํ๊ธฐ๋ ์ฝ๊ฐ ๋ ํธํ๋ค.
๐ ํฌ๋๋ฆฌ์คํธ ๋ง๋ค๊ธฐ
์ด์ ๋ญ๊ฐ ๋ง๋ค์ด๋ณผ ์๊ฐ์ด๋ค. ๋ง์นจ ๋์ธํ๋์์ ํฌ๋๋ฆฌ์คํธ ์ฑ์ ๋ง๋ค ์ผ์ด ์๊ฒจ์, ๊ธฐ์ ๋ง๋๋ ๊น์ ์ปดํฌ์ฆ๋ก ๋ง๋ค์ด๋ณด๊ธฐ๋ก ํ๋ค. ๊ธฐ๋ฅ์ ์ต๋ํ ๋จ์ํ๊ฒ ์ ํ๋ค.
- Todo ์์ฑํ๊ธฐ
- Todo ๋ด์ฉ ์์ฑํ๊ธฐ
- Todo ์๋ฃ ์ฒดํฌํ๊ธฐ
- Todo ์ญ์ ํ๊ธฐ
Firebase๋ Room์ ๋ถ์ฌ์ ์ ์ฅ์ด ๊ฐ๋ฅํ๋๋ก ๋ง๋ค๋ ค๋ค๊ฐ ์ผ๋จ ๋ณด๋ฅํ๋ค. ์ปดํฌ์ฆ์์ ViewModel, LiveData์ ์ฌ์ฉํด์ State๋ฅผ ๊ด๋ฆฌํ๋ ๋ฒ์ ๋ค๋ฃจ๋ ์ฝ๋๋ฉ์ด ์๋๋ฐ, ์ด๊ฑฐ ๊ณต๋ถํ ๋ค์์ ์ถ๊ฐํด ๋ณด๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค.
์์ฑ๋ ์ฑ ํ๋ฉด์ ๋ค์๊ณผ ๊ฐ๋ค.

๋ฉ์ธ ํ๋ฉด
@Composable
fun Main() {
var list: List<Todo> by rememberSaveable { mutableStateOf(listOf()) }
fun addTodo(todo: Todo) {
list = list + listOf(todo)
}
fun editTodo(i: Int, todo: Todo) {
list = list.toMutableList().also { it[i] = todo }
}
fun deleteTodo(i: Int) {
list = list.toMutableList().also { it.removeAt(i) }
}
val (showDialog, setShowDialog) =
rememberSaveable { mutableStateOf(false) }
var deleteItem by rememberSaveable { mutableStateOf(0) }
Scaffold(
topBar = {
TopBar(onAction = { addTodo(Todo()) })
},
) {
TodoList(
list,
onChange = { i, todo -> editTodo(i, todo) },
onDelete = {
deleteItem = it
setShowDialog(true)
}
)
DeleteDialog(showDialog, setShowDialog) {
deleteTodo(deleteItem)
}
}
}
๋ฐ์ดํฐ๋ฅผ ์ด๊ณณ์์ ์ ์ฅํ๊ณ ์์ ํ๋ค. ์ ๋ฆฌ์คํธ๋ฅผ ์ ๋ ๊ฒ ๋ณ๊ฒฝํ๋ ์ถ์ํ ๋ฐ, ์๋ก์ด ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ๋ฐ์ดํฐ์ ๋ณํ๋ฅผ ์๋ฆฌ๊ธฐ ์ํจ์ด๋ค. (๋ฌผ๋ก ๋ค๋ฅธ ์ข์ ๊ตฌํ ๋ฐฉ๋ฒ์ด ๋ง์ ๊ฒ์ด๋ค)
๋ ์ด์์์ ๋จธํฐ๋ฆฌ์ผ ๊ธฐ๋ฐ์ด๋ฏ๋ก Scaffold๋ฅผ ์ฌ์ฉํ๋ค. AlertDialog๋ ์ฌ๊ธฐ์ ๊ด๋ฆฌํ๋๋ฐ ๊ตฌํ ๋ฐฉ์์ด ์ฉ ๋ง์์ ๋ค์ง ์๋๋ค.
๋ฆฌ์คํธ
@Composable
fun TodoList(
todos: List<Todo>,
onChange: (i: Int, todo: Todo) -> Unit,
onDelete: (i: Int) -> Unit
) {
LazyColumn(...) {
itemsIndexed(items = todos) { i, todo ->
TodoItem(
item = todo,
onChange = { onChange(i, it) },
onDelete = { onDelete(i) }
)
}
}
}
๋ฆฌ์คํธ๋ฅผ ๊ทธ๋ฆฌ๋ ํจ์๋ค. RecyclerView์ ์ฝ๋ ์์ ๋น๊ตํด๋ณด๋ฉด...
์์ดํ
@Composable
fun TodoItem(item: Todo, onChange: (todo: Todo) -> Unit, onDelete: () -> Unit) {
Card(modifier = Modifier
...
.pointerInput(Unit) {
detectTapGestures(onLongPress = { onDelete() })
}
) {
Row(...) {
if (item.onEdit) {
TextField(
value = item.text,
onValueChange = { onChange(item.copy(text = it)) },
...
)
IconButton(
...
onClick = { onChange(item.copy(onEdit = false)) }) {
Icon(...)
}
} else {
Text(
text = item.text,
...
)
Checkbox(
...
checked = item.done,
onCheckedChange = { onChange(item.copy(done = it)) })
}
}
}
}
์์ฑ ์ค์ผ๋๋ TextField๊ฐ, ์์ฑ์ ์๋ฃํ์ ๋๋ Text๊ฐ ํ์๋๋๋ก ์กฐ๊ฑด๋ฌธ์ผ๋ก ๋ถ๊ธฐํ๋ค. ๊ธธ๊ฒ ํด๋ฆญํ๋ฉด ์ญ์ ํ ์ ์๋๋ก ๋ฆฌ์ค๋๋ฅผ ์ค์ ํ๋ค.
์์ดํ ์ ๋ด์ฉ์ด ๋ณํ๋ฉด ์ด๊ณณ์์ ์ค์ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๋ ๊ฒ ์๋๋ผ ๋ฐ๋ ์์ดํ ์ ์๋ก ์ฌ๋ ค๋ณด๋ธ๋ค(TodoItem -> TodoList -> Main). ์ต์ข ์ ์ผ๋ก Main()์์ ์ค์ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๋ฉด, ๋ฐ๋ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๋ฉด์ด ๋ค์ ๊ทธ๋ ค์ง๊ฒ ๋๋ค.
์ ์ฒด ์ฝ๋๋ Github์์ ํ์ธํ ์ ์๋ค.
๐ ๊ทธ๋์ ์ธ๋งํ๊ฐ์?
๋ด๊ฐ ์ ๊น ์จ๋ณด๊ณ ๋๋ Jetpack Compose์ ์ฅ๋จ์ ์ ๋ค์๊ณผ ๊ฐ๋ค.
์ฅ์
- ์ฝ๋๊ฐ ๊ฐ๊ฒฐํ๊ณ ๋๋ฆ ์ง๊ด์ ์ด๋ค.
- ๋ ์ด์์ ํ์ผ์ ๋ฐ๋ก ๊ด๋ฆฌ ์ํด๋ ๋๋ค.
- ๋ฆฌ์ฌ์ดํด๋ฌ๋ทฐ๊ฐ ์๋ค.
- ๋ ์ด์์์ ์ชผ๊ฐ์ ๊ด๋ฆฌ/์ฌ์ฌ์ฉํ๊ธฐ ์ข๋ค.
๋จ์
- ์๋์์ฑ์ด ๋ถํธํ๋ค.
- ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ ์ ๋ง ๋ถํธํ๋ค.
- ์์ง ์ง์ ์ ํ๋ ๊ธฐ๋ฅ์ด ๋ง๋ค.
(ex. LazyColumn์ ์ ๋๋ฉ์ด์ ์ง์์ด ์ ๋๋ค.)
- ๋ง๋ค๋ค ๋ง ๊ฒ ๊ฐ์ ๋๋์ด๋ค.
์ข ํฉํ๋ฉด ์ฅ์ ์ ํ์คํ๋ฐ, ์ ์ ์ถ์ ์น๊ณ ๋ ์์ง stable ํ์ง ์์ ๋๋์ด๋ค. ํธ์ํฐ ์ฑ์ด ์ปดํฌ์ฆ๋ฅผ ์ฌ์ฉํ๋ค๊ณ ํ๋๋ฐ ์ด๊ฑธ ์ด๋ป๊ฒ ํ๋ก๋ํธ์ ์ ์ฉํ๋์ง ๊ถ๊ธํด์ง๋ค. ๊ทธ ์ด๋ฉด์๋ ์๋๋ก์ด๋ ํ์ ํผ๋๋๋ฌผ์ด ์์ง ์์์๊น?
๊ทธ๋๋ ๊ตฌ๊ธ์ด ์ปดํฌ์ฆ๋ฅผ ๋ง์ด ๋ฐ์ด์ฃผ๊ณ ์์ผ๋ ์ผ๋จ ๊ณต๋ถํด๋๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค.
์๋๋ก์ด๋ ๊ฐ๋ฐ์ ์ ๊น ์ฌ์๋ ๋งํผ, ๋ง์ด ๊ณต๋ถํด์ ๊ฐ๋ ์ด๋ฆฌ๊ณ ๋ชจ๋ํ ๊ธฐ์ ๋ ์จ๋ณด๊ณ ์ถ๋ค.
