Jetpack Compose๋ฅผ ์ด์šฉํ•ด ๊ฐ„๋‹จํ•œ To-do ์•ฑ์„ ๋งŒ๋“ค๋ฉฐ ๋А๋‚€ ์ ๊ณผ ์žฅ๋‹จ์ ์— ๋Œ€ํ•ด ๋‹ค๋ฃน๋‹ˆ๋‹ค.

๐Ÿ‘€ ๋„ˆ ๋ˆ„๊ตฐ๋ฐ

๊ณ 3 ์ž…์‹œ๋ฅผ ์ค€๋น„ํ•˜๋А๋ผ ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์„ 1๋…„ ์ •๋„ ์‰ฌ์—ˆ๋‹ค. ๊ทธ๋™์•ˆ ์žˆ์—ˆ๋˜ ๊ตต์งํ•œ ๋ณ€ํ™”๋“ค์„ ์‚ดํŽด๋ณด๋‹ˆ, ์ตœ๊ทผ ํ˜œ์„ฑ๊ฐ™์ด ๋“ฑ์žฅํ•œ Jetpack Compose๊ฐ€ ์—„์ฒญ ์œ ํ–‰์ด๋ผ๊ณ  ํ•œ๋‹ค. ๋Œ€์ฒด ์ปดํฌ์ฆˆ๊ฐ€ ๋ญ๊ณ , ์–ผ๋งˆ๋‚˜ ๋Œ€๋‹จํ•˜๊ธธ๋ž˜ xml์„ ๋Œ€์ฒดํ•œ๋‹ค๋Š” ์–˜๊ธฐ๊นŒ์ง€ ๋‚˜์˜ค๋Š”๊ฑธ๊นŒ?

image.png

์›๋ž˜ ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์—์„œ๋Š” ์•ฑ์˜ ๋ ˆ์ด์•„์›ƒ(UI)์„ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด xml์„ ์‚ฌ์šฉํ•ด์™”๋‹ค. xml๋กœ ๋ ˆ์ด์•„์›ƒ์„ ๋จผ์ € ์ž‘์„ฑํ•œ ๋’ค, ์ž๋ฐ”/์ฝ”ํ‹€๋ฆฐ ์ฝ”๋“œ์—์„œ ๋ ˆ์ด์•„์›ƒ์˜ ๊ตฌ์„ฑ ์š”์†Œ(View)๋“ค์„ ํ˜ธ์ถœํ•ด์„œ ์†Œํ†ตํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์—๋Š” ๋ช‡๋ช‡ ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ œ์ ์ด ์กด์žฌํ–ˆ๋‹ค.

์ด๋ฏธ ์™„์„ฑ๋œ ์•ฑ์—์„œ ๋ฒ„ํŠผ์„ ํ•˜๋‚˜ ๋นผ๊ณ  ์‹ถ๋‹ค๊ณ  ํ•˜์ž. ๋‹จ์ˆœํžˆ xml์„ ์ˆ˜์ •ํ•ด์„œ ๋ฒ„ํŠผ์„ ์ง€์šฐ๋ฉด ๋  ๊ฒƒ ๊ฐ™์ง€๋งŒ, ํ•ด๋‹น ๋ฒ„ํŠผ๊ณผ ์—ฐ๊ฒฐ๋œ Java/Kotlin ์ฝ”๋“œ๋ฅผ ํ•จ๊ป˜ ์‚ญ์ œํ•˜์ง€ ์•Š์œผ๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

5๊ฐœ์˜ ์ž…๋ ฅ ์นธ์ด ์žˆ๋Š” ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด์„ ๋งŒ๋“ค๊ณ ์ž ํ•œ๋‹ค. ์ž…๋ ฅ ์นธ์ด ๋ชจ๋‘ ๋˜‘๊ฐ™์ด ์ƒ๊ฒผ๋”๋ผ๋„, 5๊ฐœ์˜ EditText๋ฅผ ํ•˜๋‚˜ํ•˜๋‚˜ ๋งŒ๋“ค์–ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

xml ์–ธ์–ด ์ž์ฒด์˜ ํŠน์„ฑ๊ณผ ์ƒ์ˆ ํ•œ ์žฌ์‚ฌ์šฉ์„ฑ ๋ฌธ์ œ๋กœ ์ธํ•ด, ๋ ˆ์ด์•„์›ƒ์ด ๋ณต์žกํ• ์ˆ˜๋ก ์ฝ”๋“œ๊ฐ€ ๋น„๋Œ€ํ•ด์ง€๊ณ  ์•Œ์•„๋ณด๊ธฐ ์–ด๋ ค์›Œ์ง„๋‹ค.

๋˜ ๊ธฐ์กด์˜ View ์‹œ์Šคํ…œ์€ ์‹œ๊ฐ„์ด ํ๋ฅผ์ˆ˜๋ก ์ƒ์†๊ณผ ๋ ˆ๊ฑฐ์‹œ ๋ฉ์–ด๋ฆฌ๊ฐ€ ๋˜์–ด๋ฒ„๋ ค์„œ, ์œ ์ง€๋ณด์ˆ˜ํ•˜๋Š” ์ž…์žฅ์—์„œ๋„ ๊ณ ๋ฏผ์ด ์ด๋งŒ์ €๋งŒ์ด ์•„๋‹ˆ์—ˆ์„ ๊ฒƒ์ด๋‹ค. (์•„ํ‹ฐํด ์ž‘์„ฑ์ผ ๊ธฐ์ค€ View.java์˜ ์ฝ”๋“œ ๊ธธ์ด๋Š” 30409์ค„์ด๋‹ค.)

image.png
image.png

Jetpack Compose๋Š” ๋„ค์ดํ‹ฐ๋ธŒ UI๋ฅผ ๋นŒ๋“œํ•˜๊ธฐ ์œ„ํ•œ Android์˜ ์ตœ์‹  ๋„๊ตฌ ํ‚คํŠธ์ž…๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์ด๋ฅผ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด Kotlin๊ณผ ์„ ์–ธํ˜• UI๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋งŒ๋“ค์–ด์ง„ ๊ฒƒ์ด Jetpack Compose๋‹ค. ์ปดํฌ์ฆˆ์˜ ๊ตฌ์„ฑ ์š”์†Œ๋“ค์€ ๋ชจ๋‘ Kotlin ํ•จ์ˆ˜๋กœ ๋งŒ๋“ค์–ด์ ธ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ์žฌ์‚ฌ์šฉ์ด ์‰ฝ๊ณ  ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•˜๋‹ค๋Š” ์žฅ์ ์ด ์žˆ๋‹ค. ๋ ˆ์ด์•„์›ƒ์„ ์ž‘์„ฑํ•˜๋ ค๊ณ  ์—ฌ๋Ÿฌ ์–ธ์–ด๋ฅผ ๋ฐฐ์šธ ํ•„์š”๋„ ์—†๊ณ , ๊ตฌ๊ธ€ ์™ˆ ์ง๊ด€์ ์ด๊ธฐ๊นŒ์ง€ ํ•˜๋‹ค. ์—„์ฒญ ์ข‹์•„๋ณด์ธ๋‹ค. ๊ทผ๋ฐ ์ด๊ฑฐ ์–ด๋””์„œ ๋ณธ ๊ฒƒ ๊ฐ™์€๋ฐ...

30fviu.jpg

ํ”Œ๋Ÿฌํ„ฐ๋ž‘ ๋ฌธ๋ฒ•์ , ๊ตฌ์กฐ์ ์œผ๋กœ ๋น„์Šทํ•œ ๋ถ€๋ถ„์ด ๋งŽ๋‹ค. ๊ฐ™์€ ๊ตฌ๊ธ€์—์„œ ๋งŒ๋“ค๊ธฐ๋„ ํ•˜๊ณ , ๋‘˜ ๋‹ค ๊ธฐ๋ณธ์ ์œผ๋กœ ๋จธํ‹ฐ๋ฆฌ์–ผ ๋””์ž์ธ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ณ  ์žˆ์–ด์„œ ๊ทธ๋Ÿฐ ๊ฒƒ ๊ฐ™๋‹ค. ๊ทผ๋ฐ ๋‚˜์ค‘์— ํ”Œ๋Ÿฌํ„ฐ๊ฐ€ ์ถฉ๋ถ„ํžˆ stable ํ•ด์ง€๋ฉด ์ปดํฌ์ฆˆ๋Š” 'iOS ์•ˆ๋˜๋Š” ๋ฐ˜์ชฝ์งœ๋ฆฌ ํ”Œ๋Ÿฌํ„ฐ'๊ฐ€ ๋˜๋Š” ๊ฒŒ ์•„๋‹๊นŒ? (์ด๋ฏธ์ง€: Reddit)

๐Ÿค” ์ผ๋‹จ ์จ๋ณด์ž

image.png

์ผ๋‹จ ํ•œ๋ฒˆ ์จ๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค. ์šฐ์„  Compose ๊ธฐ์ดˆ ์ฝ”๋“œ๋žฉ์„ ์ฐพ์•„์„œ ์ˆ˜๊ฐ•ํ–ˆ๋‹ค. ํ•ด๋‹น ์ฝ”๋“œ๋žฉ์—์„œ๋Š” ์ปดํฌ์ฆˆ์˜ ๊ธฐ์ดˆ์ ์ธ ๋™์ž‘ ์›๋ฆฌ์™€ UI ์ž‘์„ฑ ๋ฐฉ๋ฒ•, State์™€ LazyColumn(RecyclerView ๋Œ€์ฒด)์— ๋Œ€ํ•ด ๋‹ค๋ฃจ๊ณ  ์žˆ๋‹ค. ์œ„ ์ด๋ฏธ์ง€์ฒ˜๋Ÿผ ํŽผ์ณ์ง€๋Š” ๋ฆฌ์ŠคํŠธ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์ตœ์ข… ๋ชฉํ‘œ๋‹ค.

์ผ๋‹จ ์•ˆ๋“œ๋กœ์ด๋“œ ์ง„์˜ ์ตœ๋Œ€ ๋นŒ๋Ÿฐ์ธ RecyclerView๋ฅผ ์•ˆ ์จ๋„ ๋˜๋Š”๊ฒŒ ๋„ˆ๋ฌด ์ข‹์•˜๋‹ค. ๋ฆฌ์ŠคํŠธ ํ•˜๋‚˜ ๋„์šฐ๋Š”๋ฐ ViewGroup, LayoutManager, ViewHolder, Adapter, DiffUtil, Item layout... ์ด๊ฒŒ ๋‹ค ํ•„์š”ํ•˜๋‹ค๋Š”๊ฒŒ ๋ง์ด ๋˜๋‚˜? ์ปดํฌ์ฆˆ์—์„œ๋Š” LazyColumn์— ๋ฐ์ดํ„ฐ๋งŒ ๋„ฃ์œผ๋ฉด ์•Œ์•„์„œ ๊ทธ๋ ค์ค€๋‹ค.

์ปดํฌ์ฆˆ์˜ ์„ ์–ธํ˜• UI๋„ ์ฒ˜์Œ์—๋Š” ๋‚œํ•ดํ•œ๋ฐ ์“ฐ๋‹ค ๋ณด๋ฉด ์กฐ๊ธˆ์”ฉ ์ ์‘๋˜๋Š” ๋А๋‚Œ์ด๋‹ค. ํ™”๋ฉด ๊ตฌ์„ฑ ์š”์†Œ๋“ค์„ ํ•จ์ˆ˜๋กœ ์ž˜๊ฒŒ ์ชผ๊ฐค ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ค‘์ฒฉ๋œ ๋ ˆ์ด์•„์›ƒ์„ ๊ด€๋ฆฌํ•˜๊ธฐ๋„ ์•ฝ๊ฐ„ ๋” ํŽธํ–ˆ๋‹ค.

๐Ÿƒ ํˆฌ๋‘๋ฆฌ์ŠคํŠธ ๋งŒ๋“ค๊ธฐ

์ด์ œ ๋ญ”๊ฐ€ ๋งŒ๋“ค์–ด๋ณผ ์‹œ๊ฐ„์ด๋‹ค. ๋งˆ์นจ ๋Œ€์™ธํ™œ๋™์—์„œ ํˆฌ๋‘๋ฆฌ์ŠคํŠธ ์•ฑ์„ ๋งŒ๋“ค ์ผ์ด ์ƒ๊ฒจ์„œ, ๊ธฐ์™• ๋งŒ๋“œ๋Š” ๊น€์— ์ปดํฌ์ฆˆ๋กœ ๋งŒ๋“ค์–ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค. ๊ธฐ๋Šฅ์€ ์ตœ๋Œ€ํ•œ ๋‹จ์ˆœํ•˜๊ฒŒ ์ •ํ–ˆ๋‹ค.

Firebase๋‚˜ Room์„ ๋ถ™์—ฌ์„œ ์ €์žฅ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ๋งŒ๋“ค๋ ค๋‹ค๊ฐ€ ์ผ๋‹จ ๋ณด๋ฅ˜ํ–ˆ๋‹ค. ์ปดํฌ์ฆˆ์—์„œ ViewModel, LiveData์„ ์‚ฌ์šฉํ•ด์„œ State๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฒ•์„ ๋‹ค๋ฃจ๋Š” ์ฝ”๋“œ๋žฉ์ด ์žˆ๋˜๋ฐ, ์ด๊ฑฐ ๊ณต๋ถ€ํ•œ ๋‹ค์Œ์— ์ถ”๊ฐ€ํ•ด ๋ณด๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

์™„์„ฑ๋œ ์•ฑ ํ™”๋ฉด์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

Screen_Recording_20211128-174326_TODO_1.gif

๋ฉ”์ธ ํ™”๋ฉด

Kotlin
@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๋„ ์—ฌ๊ธฐ์„œ ๊ด€๋ฆฌํ•˜๋Š”๋ฐ ๊ตฌํ˜„ ๋ฐฉ์‹์ด ์ฉ ๋งˆ์Œ์— ๋“ค์ง„ ์•Š๋Š”๋‹ค.

๋ฆฌ์ŠคํŠธ

Kotlin
@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์™€ ์ฝ”๋“œ ์–‘์„ ๋น„๊ตํ•ด๋ณด๋ฉด...

์•„์ดํ…œ

Kotlin
@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 -&gt; TodoList -&gt; Main). ์ตœ์ข…์ ์œผ๋กœ Main()์—์„œ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด, ๋ฐ”๋€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ™”๋ฉด์ด ๋‹ค์‹œ ๊ทธ๋ ค์ง€๊ฒŒ ๋œ๋‹ค.

์ „์ฒด ์ฝ”๋“œ๋Š” Github์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿš€ ๊ทธ๋ž˜์„œ ์“ธ๋งŒํ•œ๊ฐ€์š”?

๋‚ด๊ฐ€ ์ž ๊น ์จ๋ณด๊ณ  ๋А๋‚€ Jetpack Compose์˜ ์žฅ๋‹จ์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

์žฅ์ 

๋‹จ์ 

(ex. LazyColumn์€ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ง€์›์ด ์•ˆ ๋œ๋‹ค.)

์ข…ํ•ฉํ•˜๋ฉด ์žฅ์ ์€ ํ™•์‹คํ•œ๋ฐ, ์ •์‹ ์ถœ์‹œ ์น˜๊ณ ๋Š” ์•„์ง stable ํ•˜์ง€ ์•Š์€ ๋А๋‚Œ์ด๋‹ค. ํŠธ์œ„ํ„ฐ ์•ฑ์ด ์ปดํฌ์ฆˆ๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค๊ณ  ํ•˜๋˜๋ฐ ์ด๊ฑธ ์–ด๋–ป๊ฒŒ ํ”„๋กœ๋•ํŠธ์— ์ ์šฉํ–ˆ๋Š”์ง€ ๊ถ๊ธˆํ•ด์ง„๋‹ค. ๊ทธ ์ด๋ฉด์—๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ ํŒ€์˜ ํ”ผ๋•€๋ˆˆ๋ฌผ์ด ์žˆ์ง€ ์•Š์•˜์„๊นŒ?

๊ทธ๋ž˜๋„ ๊ตฌ๊ธ€์ด ์ปดํฌ์ฆˆ๋ฅผ ๋งŽ์ด ๋ฐ€์–ด์ฃผ๊ณ  ์žˆ์œผ๋‹ˆ ์ผ๋‹จ ๊ณต๋ถ€ํ•ด๋‘๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.


์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์„ ์ž ๊น ์‰ฌ์—ˆ๋˜ ๋งŒํผ, ๋งŽ์ด ๊ณต๋ถ€ํ•ด์„œ ๊ฐ๋„ ์‚ด๋ฆฌ๊ณ  ๋ชจ๋˜ํ•œ ๊ธฐ์ˆ ๋„ ์จ๋ณด๊ณ  ์‹ถ๋‹ค.