08
16

@Composable ๊ณผ Recomposition, ๊ทธ๋ฆฌ๊ณ  Modifier

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Text(
        text = "Hello $name!",
        modifier = modifier
    )
}

์ปดํฌ์ €๋ธ” ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ ํ•จ์ˆ˜๋Š” UI๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๊ฐ€ ๋œ๋‹ค. ์ด ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ ํ•จ์ˆ˜๋Š” ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋”ฐ๋กœ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ณ , ๋Ÿฐํƒ€์ž„๋•Œ ํ†ตํ•ฉํ•˜๊ธฐ ์œ„ํ•ด ์ฝ”๋“œ๊ฐ€ ๋”ฐ๋กœ ์ƒ์„ฑ๋œ๋‹ค. ์ค‘์š”ํ•œ ์ ์€ UI๋ฅผ ์ƒ์„ฑํ•˜๋Š”๋ฐ, returnํ˜•์‹์ด ์•„๋‹Œ emit ํ˜•์‹์ด์–ด์„œ ๋ฐ˜ํ™˜๊ฐ’์ด ๋”ฐ๋กœ ์—†๋‹ค๋Š” ์ ์ด๋‹ค. 

UI๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ์š”์†Œ์ด๊ธฐ ๋•Œ๋ฌธ์— Recomposition์— ๋Œ€ํ•œ ๊ฐœ๋…๋„ ์—ฎ์—ฌ์žˆ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด Compose๋Š” ์ƒˆ ๋ฐ์ดํ„ฐ๋กœ Composableํ•จ์ˆ˜๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•˜์—ฌ ์—…๋ฐ์ดํŠธ๋œ UI๋ฅผ ๋งŒ๋“ ๋‹ค. ๊ฒฝ์šฐ์— ๋”ฐ๋ผ ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ UI ์—…๋ฐ์ดํŠธํ•ด์„œ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•ด์ฃผ๋Š”๋ฐ, ๊ฐ์ฒด์˜ reference๋งŒ ๋น„๊ตํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ๋‚ด์šฉ๊นŒ์ง€ ๋น„๊ตํ•ด์„œ ๋‚ด์šฉ์ด ๊ฐ™์€๊ฒฝ์šฐ ๋”ฐ๋กœ ์žฌ๊ตฌ์„ฑ์„ ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ทธ๋Ÿผ ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ์–ด๋–ป๊ฒŒ ๊ฐ์ง€ํ• ๊นŒ? ๊ทธ๊ฑด ์ƒํƒœ ๊ฐ์ฒด์— ๋‹ฌ๋ ค์žˆ๋‹ค. MutableState, State๋กœ ๊ฐ์‹ธ์ง„ ์ƒํƒœ๊ฐ์ฒด๊ฐ€ ์žˆ๋Š”๋ฐ, ์–˜๋„ค๊ฐ€ UI recomposition์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ๊ฐ์ฒด๋“ค์ด๋‹ค.

 

์ด ๊ฐ์ฒด๋“ค์€ ๊ฐ’ ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ•  ๋•Œ Compose ์‹œ์Šคํ…œ์ด ์•Œ ์ˆ˜ ์žˆ๋‹ค. ๋˜ remeber๊ฐ€ ๋ถ™์€ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด์ „ ์—ฐ์‚ฐ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์™€์„œ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฆฌ์ปดํฌ์ง€์…˜์„ ๋ฐฉ์ง€ํ•˜๋Š” ํšจ๊ณผ๊ฐ€ ์žˆ๋‹ค.

Column, Row, Box ์ปดํฌ์ €๋ธ”์€ ๋ชจ๋‘ ์ปจํ…Œ์ด๋„ˆ๋‹ค.

@Preview(showBackground = true)
@Composable
fun UserProfile() {
    Column {
        Text("์œ ์ €")
        Text("abcd@a.b")
        var likes by remember { mutableStateOf(0) }
        Text("Likes: $likes")
        Button(onClick = { likes++ }) {
            Text("Like")
        }
    }
}

@Preview๋Š” ์ •์  ํ™”๋ฉด์„ ๋ฏธ๋ฆฌ ๋ณด์—ฌ์ฃผ๋Š” ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ, ์‹ค ๊ธฐ๊ธฐ ํ™”๋ฉด์ฒ˜๋Ÿผ ๋ณด๊ณ ์‹ถ๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•˜๋ฉด ๋œ๋‹ค.

@Preview(showBackground = true, widthDp = 320)

์ฝ”๋“œ๋กœ ๋Œ์•„์™€์„œ, ์žฌ๊ตฌ์„ฑ์ด ๋˜๋Š” ๋ถ€๋ถ„์€ `Text("Likes: $likes")` ์ด๋ถ€๋ถ„์ด๋‹ค. Text๋กœ Like๊ฐ€ ์ ํ˜€์žˆ๋Š” Button์„ ๋ˆ„๋ฅด๋ฉด likes๊ฐ’์ด ์ฆ๊ฐ€ํ•ด์„œ ์ด TextComposable์ด ์žฌ๊ตฌ์„ฑ๋œ๋‹ค. UserProfile Composable ์ „์ฒด๊ฐ€ ์žฌ๊ตฌ์„ฑ๋˜์ง€๋Š” ์•Š๋Š”๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ๋”ฐ๋กœ ์•ˆ๋งŒ๋“ค์—ˆ์ง€๋งŒ, like๋ฅผ ๋ณ€๊ฒฝํ–ˆ๋Š”๋ฐ ์ด์ „๊ณผ ๊ฐ’์ด ๊ฐ™๋‹ค๋ฉด ์žฌ๊ตฌ์„ฑ์ด ์ผ์–ด๋‚˜์ง€ ์•Š๋Š”๋‹ค.

`var likes by remember { mutableStateOf(0) }`

์ด ๋ถ€๋ถ„์ด ์ด๋ฒˆ Composable์˜ ํ•ต์‹ฌ์ด๋‹ค. by๋กœ ์œ„์ž„์‹œํ‚จ๊ฒƒ๋ถ€ํ„ฐ ๋ด์•ผ๋œ๋‹ค.

`val likes = remember { mutableStateOf(0) }`

์ด๊ฒŒ ์œ„์ž„์‹œํ‚ค๊ธฐ ์ „ ํ˜•ํƒœ๋‹ค.  MutableState๊ฐ’์„ remember๋กœ ๊ฐ์‹ธ์„œ likes ์— ํ• ๋‹นํ•œ ํ˜•ํƒœ๋กœ, ๊ฐ’์— ์ ‘๊ทผํ• ๋•Œ๋งˆ๋‹ค .value ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ๋œ๋‹ค. get, set์ด MutableState ๊ฐ์ฒด ์•ˆ์— ์žˆ๋Š”๋ฐ ๊ฑฐ๊ธฐ๊ฒŒ value ํ”„๋กœํผํ‹ฐ๋กœ ์ ‘๊ทผํ•œ๋‹ค๊ณ  ๋ณด๋ฉด ๋œ๋‹ค. LiveData๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋ดค๋˜ ๊ฒฝ์šฐ๋‹ค. remember๋กœ ๊ฐ์‹ธ๊ฒŒ ๋˜๋ฉด,  recomposition ์‚ฌ์ด์—์„œ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ดˆ๊ธฐํ™” ํ•  ๋•Œ๋งŒ ๋žŒ๋‹ค ๋‚ด๋ถ€์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ , ์žฌ๊ตฌ์„ฑ์ด ๋ฐœ์ƒํ•  ๋•Œ๋Š” ์ €์žฅ๋œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ˜•ํƒœ๋‹ค. ๋”ฐ๋ผ์„œ ๋งค๋ฒˆ MutableState๊ฐ์ฒด๋ฅผ ๋ฐฐ์ •ํ•ด์„œ ์“ฐ๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ remeber๋œ ๊ฑธ ๊บผ๋‚ด์„œ ์“ฐ๊ฒŒ ๋œ๋‹ค.

 

by๋กœ ์œ„์ž„ํ•˜๊ฒŒ ๋˜๋ฉด, get, set์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด value ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋˜ ๊ท€์ฐฎ์€ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค. ๋ฐ”๋กœ likes๋กœ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•˜๋‹ค.  `likes.value = likes.value+1` ์ด `likes++` ๋กœ ๋ฐ”๋€Œ์—ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ์ฒด๊ฐ๋  ๊ฒƒ์ด๋‹ค.

# Modifier

`Modifier`๋Š” Compose์—์„œ ๊ฑฐ์˜ xml์—์„œ ํ• ์ˆ˜์žˆ๋Š” ๋ชจ๋“  ๊ฒƒ๋“ค์„ ์ •์˜ํ•˜๋Š” ๊ฐ์ฒด์ด๋ฉฐ, ๋”ฐ๋กœ ์ง€์ •ํ•˜์ง€์•Š์œผ๋ฉด ๊ธฐ๋ณธ modifier๊ฐ€ ์‚ฌ์šฉ๋œ๋‹ค. ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์ฒด์ด๋‹์„ ์“ด๋‹ค.

Modifier
    .padding(16.dp)
    .background(Color.Gray)
    .clickable { /* ํด๋ฆญ ๋™์ž‘ */ }

์ปดํฌ์ฆˆ์—์„œ๋Š” margin์„ ์ง€์ •ํ•˜์ง€ ์•Š๊ณ  padding๋งŒ ์ง€์ •ํ•œ๋‹ค. margin์„ ๋ช…์‹œ์ ์œผ๋กœ ํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ offset์œผ๋กœ ์œ„์น˜ ์กฐ์ •๋งŒ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋˜๊ฒ ๋‹ค. match_parent๊ฐ™์€ ๊ฑด `fillMaxWidth, fillMaxHeight, fillMaxSize` ๊ฐ™์€๊ฑธ๋กœ ์ ‘๊ทผํ•˜๋ฉด ๋œ๋‹ค. ํด๋ฆญ๋ฆฌ์Šค๋„ˆ๋„ ์—ฌ๊ธฐ์„œ ๋‹ฌ๋ฉด ๋œ๋‹ค.

๊ทผ๋ฐ xml๊ณผ ์•„์ฃผ ๋‹ค๋ฅธ์ ์ด ์กด์žฌํ•œ๋‹ค. ์ฒด์ด๋‹ ์ˆœ์„œ๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ๋‹ค๋ฅด๊ฒŒ ๋งŒ๋“ ๋‹ค๋Š” ์ ์ด๋‹ค.

@Preview(showBackground = true)
@Composable
fun TestBox() {
    Column {
        Box(modifier = Modifier
            .size(200.dp)
            .padding(40.dp)
            .background(Color.Red)) {
        }
        Box(modifier = Modifier.size(0.dp, 20.dp))
        Box(modifier = Modifier
            .padding(40.dp)
            .size(200.dp)
            .background(Color.Red)) {
        }
    }
}

์‚ฌ์ด์ฆˆ๋ฅผ ์ง€์ •ํ•˜๊ณ  ํŒจ๋”ฉ์„ ์ค€๊ฒƒ๊ณผ, ํŒจ๋”ฉ์„ ๋จผ์ € ์ฃผ๊ณ  ์‚ฌ์ด์ฆˆ๋ฅผ ์ง€์ •ํ•œ ๊ฒƒ์€ ์•„์˜ˆ ๋‹ค๋ฅด๋‹ค. ์ „์ž๋Š” ์ปจํ…์ธ  ํฌ๊ธฐ๊ฐ€ 160, ํ›„์ž๋Š” 200์ด๋‹ค.

then์„ ์‚ฌ์šฉํ•ด์„œ ์กฐ๊ฑด๋ถ€๋กœ ๋‹ค๋ฅธ Modifier๋ฅผ ๋ถ™์ด๋Š” ์ž‘์—…๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

@Preview(showBackground = true)
@Composable
fun TestBox() {
    var isSelected by remember { mutableStateOf(false) }

    Column {
        Box(
            modifier = Modifier
                .size(200.dp)
                .padding(40.dp)
                .background(Color.Red)
        ) {
        }
        Box(modifier = Modifier.size(0.dp, 20.dp))
        Box(
            modifier = Modifier.padding(40.dp)
                .size(200.dp)
                .then(
                    if (isSelected) Modifier
                        .background(Color.Blue)
                    else Modifier
                        .background(Color.Red)
                )
        )
        Button(onClick = { isSelected = !isSelected }) {
            Text(if (isSelected) "์„ ํƒ๋จ" else "์„ ํƒ ์•ˆ๋จ")
        }
    }
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ๋•Œ, isSelected๊ฐ’์ด ํ† ๊ธ€๋˜๋ฉด์„œ ํ•˜๋‹จ Box์˜ Modifier๊ฐ€ ๊ฐ™์ด ํ† ๊ธ€๋œ๋‹ค.

# Surface 

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Surface(color = MaterialTheme.colorScheme.primary) {
        Text(
            text = "Hello $name!",
            modifier = modifier
        )
    }
}

์ปดํฌ์ฆˆ์—์„œ Surface๋Š” ์ปดํฌ์ €๋ธ” ์ปจํ…Œ์ด๋„ˆ ์ค‘ ํ•˜๋‚˜๋‹ค. ๊ทผ๋ฐ ์ด์ œ Material Design์„ ๊ณ๋“ค์ธ ํ˜•ํƒœ๋กœ, ์•Œ์•„์„œ ํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด ๋งŽ์€ ์ปดํฌ๋„ŒํŠธ๋‹ค. ๋จธํ„ฐ๋ฆฌ์–ผ ๋””์ž์ธ์—์„œ ๋‚˜์˜จ ์ปดํฌ๋„ŒํŠธ ๋‹ต๊ฒŒ, color์— primary๋กœ ์ง€์ •ํ•˜๋ฉด, colorScheme์— ์กด์žฌํ•˜๋Š” primary ๋Œ€์‘ํ•˜๋Š” onPrimary์ƒ‰๊น”๋กœ ์ปดํฌ๋„ŒํŠธ ๋Œ€๋น„๋ฅผ ์ด๋ค„์„œ ๊ฐ€๋…์„ฑ์„ ๋ณด์žฅํ•œ๋‹ค. 

# ํ˜ธ์ด์ŠคํŒ…๊ณผ ์ƒํƒœ๊ด€๋ฆฌ

์›น ๊ฐœ๋ฐœ์—์„œ ์ฒ˜์Œ ์ ‘ํ•œ ๋‹จ์–ด์ธ๋ฐ, ์ปดํฌ์ฆˆ์—๋„ ๋“ฑ์žฅํ•œ๋‹ค.

Composable ํ•จ์ˆ˜์—์„œ ์—ฌ๋Ÿฌ ๊ณณ์— ์“ฐ์ด๋Š” ์ƒํƒœ๋Š” ๊ณตํ†ต ์ƒ์œ„ํ•ญ๋ชฉ์— ์œ„์น˜ํ•ด์•ผ๋œ๋‹ค. ์ด๊ฑธ ์ƒํƒœ ํ˜ธ์ด์ŠคํŒ…์ด๋ผ๊ณ  ๋งํ•˜๋ฉฐ ํ•˜์œ„ ์ปดํฌ์ €๋ธ”์—์„œ ์ƒํƒœ ์žฌ์‚ฌ์šฉ์ด ๊ฐ„ํŽธํ•ด์„œ ์ข‹์€ ๋ฐฉ๋ฒ•์ด๋‹ค. Datasource๊ฐ€ ์ด๋Ÿฐ ๊ณณ์— ์†ํ•œ๋‹ค.

flowchart LR
    B(์ƒํƒœ์ œ๊ณต - MutableStateOf ๊ฐ์ฒด)
    B --> D[A]
    B --> E[B]
    B --> F[C]

์ƒํƒœ๊ฐ’์„ ์ƒ์œ„์š”์†Œ๋ž‘ ๊ณต์œ ํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ์ƒํƒœ๊ฐ’์— ์•ก์„ธ์Šค ํ•ด์•ผ๋˜๋Š” ๊ณตํ†ต ์ƒ์œ„ ์š”์†Œ ์ชฝ์œผ๋กœ ์ƒํƒœ๊ฐ’์„ ์ด๋™์‹œํ‚ค๋ฉด ๋œ๋‹ค.

์ฆ‰ ๋‹จ๋ฐฉํ–ฅ์„ฑ์œผ๋กœ ์ƒ์œ„ ์š”์†Œ๋Š” ํ•˜์œ„ ์š”์†Œ๋กœ ์ƒํƒœ๋ฅผ ์ „๋‹ฌํ•˜๊ณ , ํ•˜์œ„ ์š”์†Œ๋Š” ๊ทธ๊ฑธ ๋ฐ›์•„ ์ด๋ฒคํŠธ๋ฅผ ์ƒ์œ„ ์š”์†Œ๋กœ ์ œ๊ณตํ•˜๋Š”, ๊ฑฐ์˜ ์›น ๊ฐœ๋ฐœ์—์„œ์˜ ํ˜ธ์ด์ŠคํŒ…๊ณผ ๋™์ผํ•œ ํ˜•ํƒœ๋‹ค. ๊ฐ„๋‹จํ•œ ์นด์šดํ„ฐ ์ฝ”๋“œ๋ž‘ ๊ฐ™์ด ๋ณด์ž.

@Composable
fun ParentComponent() {
    var count by remember { mutableStateOf(0) }
    
    HoistedCounter(
        count = count,
        onIncrement = { count++ }
    )
}

์ตœ์ƒ์œ„ ์ปดํฌ์ €๋ธ”์ด๋‹ค. ์—ฌ๊ธฐ์„œ count๋ฅผ ๊ด€๋ฆฌํ•œ๋‹ค.

@Composable
fun HoistedCounter(
    count: Int,
    onIncrement: () -> Unit
) {
    Column {
        Button(onClick = onIncrement) {
            Text("์ฆ๊ฐ€์ฆ๊ฐ€")
        }
        Text("๊ฐœ์ˆ˜: $count")
    }
}

์ƒํƒœ๋ฅผ Parentํ•œํ…Œ ๋ฐ›๊ณ , ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ๋™์ž‘์„ ๋žŒ๋‹ค๋กœ ๋„˜๊ฒจ ๋ฐ›๋Š”๋‹ค. ๊ทธ๋ž˜์„œ ์ด ํ˜ธ์ด์ŠคํŒ… ๋ฐ›๋Š” ์ปดํฌ์ €๋ธ”์—์„œ๋Š” ํด๋ฆญ์ด๋ฒคํŠธ๋งŒ ๊ด€๋ฆฌํ•˜๊ฒŒ ๋œ๋‹ค. ์–ด๋”˜๊ฐ€ ์ต์ˆ™ํ•˜๊ฒŒ ๋Š๊ปด์ง„๋‹ค๋ฉด, recyclerview adapter๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ํด๋ฆญ์ด๋ฒคํŠธ๋ฅผ ์ด๋ ‡๊ฒŒ ๋„˜๊ธด ํ˜•ํƒœ๊ฐ€ ์žˆ์–ด์„œ๋‹ค. ํ˜ธ์ด์ŠคํŒ…์ด ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ๋‹จ๋ฐฉํ–ฅ์œผ๋กœ ํ•ด์ฃผ๊ธด ํ•˜์ง€๋งŒ, ๋„ˆ๋ฌด depth๊ฐ€ ๊นŠ์–ด์ง€๋ฉด ๋ฆฌ์ปดํฌ์ง€์…˜์ด ๋ฐœ์ƒํ•  ์ˆ˜๋„ ์žˆ์–ด์„œ ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ํŒŒํŽธํ™”ํ•ด์„œ ์ƒํƒœ๋ฅผ ์ข€ ๋” ์ž‘์€ ๋‹จ์œ„๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ธฐ์ˆ ์ด ํ•„์š”ํ•˜๋‹ค.

 

๋ฆฌ์ปดํฌ์ง€์…˜์„ ์ตœ์†Œํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์ด์ „์— ์‚ฌ์šฉํ–ˆ์—ˆ๋˜ remember๋ฅผ ์„ ํƒํ•  ์ˆ˜๋„ ์žˆ๊ณ , key๋‚˜ derivedStateOf๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์บ์‹ฑํ•ด๋‘๋Š” ๋ฐฉ์‹์œผ๋กœ ์ข€ ๋” ์„ฑ๋Šฅ์„ ๋Œ์–ด์˜ฌ๋ฆด ์ˆ˜ ์žˆ๋‹ค. remeber๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ฃผ์˜ํ•  ์ ์€, ์ปดํฌ์ €๋ธ”์ด ์ปดํฌ์ง€์…˜์— ์œ ์ง€๋˜๋Š” ๋™์•ˆ์—๋งŒ ์ž‘๋™ํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ๋ž˜์„œ ํ™”๋ฉดํšŒ์ „๊ณผ ๊ฐ™์€ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋‚ ์•„๊ฐ„๋‹ค. ๊ทธ๋•Œ๋Š” rememberSaveable์„ ์จ์ฃผ๋ฉด ๋๋‚œ๋‹ค.

val processedItems by remember(items) {
    derivedStateOf { items.map { it.uppercase() } }
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด items์—์„œ ๋ณ€๊ฒฝ์ด ์žˆ์„๋•Œ๋งŒ map ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. ๋žŒ๋‹ค์— remember๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋งค ๋ฆฌ์ปดํฌ์ง€์…˜ ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ๋žŒ๋‹ค๊ฐ€ ์ƒ์„ฑ๋˜๋Š”๊ฑธ ๋ง‰๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ตœ์ ํ™”๋ฅผ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

 

key๊ฐ€ ์ค‘์š”ํ•œ๋ฐ, ์ปดํฌ์ฆˆ๋Š” key ๊ฐ’์œผ๋กœ ์ด์ „ ์ปดํฌ์ง€์…˜, ์ƒˆ ์ปดํฌ์ง€์…˜์˜ ์ฐจ์ด๋ฅผ ๊ตฌ๋ถ„ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ๋™์ผํ•œ ํ‚ค๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉด ์žฌ์‚ฌ์šฉ๋˜๊ณ , ์•„๋‹ˆ๋ผ๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑํ•œ๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ ๋ฆฌ์ŠคํŠธ๊ฐ€ ์ฃผ์–ด์งˆ ๋•Œ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹๋‹ค. ๋งŒ์•ฝ ๋งค๋ฒˆ ๋ฆฌ์ŠคํŠธ ๋‚ด์˜ ์ˆœ์„œ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์„ฑ๋Šฅ์ด ๋–จ์–ด์งˆ ์ˆ˜ ๋„ ์žˆ๋‹ค. key๋ฅผ ์“ฐ๋ฉด ๊ทธ๊ฒƒ๋งŒ ์—…๋ฐ์ดํŠธ ํ•˜๋ฉด ๋ผ์„œ ๋ฆฌ์ปดํฌ์ง€์…˜ ์—†๋Š” ํšจ์œจ์ ์ธ ์ž‘์—…์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

@Composable
fun KeyTestList(items: List<String>) {
    Column {
        items.forEachIndexed { index, item ->
            key(index) {
                Text(item)
            }
        }
    }
}

@Composable
fun MultipleKeysExample(items: List<Item>) {
    Column {
        items.forEach { item ->
            key(item.id, item.category) {
                ItemComponent(item)
            }
        }
    }
}

ํ‚ค๋ฅผ ๋ช‡๊ฐœ ์กฐํ•ฉํ•ด์„œ ๋ณตํ•ฉํ‚ค๋กœ ์“ธ์ˆ˜๋„ ์žˆ๋‹ค. ์ด๋Ÿฐ ์ƒํƒœ๊ด€๋ฆฌ๋Š” ๋Œ€๊ทœ๋ชจ ๋ฆฌ์ŠคํŠธ ๊ฐ™์€ ๊ณณ์—์„œ ๋น›์„ ๋ฐœํ•˜๋Š”๋ฐ, ์ง€๊ธˆ๋ถ€ํ„ฐ ์•Œ์•„๋ณด์ž. 

 

# `LazyColumn, LazyRow, LazyVerticalGrid...`

์ด ์นœ๊ตฌ๋“ค์€ ์™„๋ฒฝํ•˜๊ฒŒ RecyclerView์˜ ์ƒ์œ„ํ˜ธํ™˜์ด๋‹ค. ์ฝ”๋“œ ๋ช‡์ค„๋กœ ๊ธฐ์กด์˜ RecyclerViewAdapter๋ฅผ ์™„์ „ํžˆ ๋Œ€์ฒดํ•ด์ค€๋‹ค.

@Composable
private fun Greetings(
    modifier: Modifier = Modifier,
    names: List<String> = List(1000) { "$it" }
) {
    LazyColumn(modifier = modifier.padding(vertical = 4.dp)) {
        items(items = names) { name ->
            Greeting(name = name)
        }
    }
}

์•„์ดํ…œ 1000๊ฐœ์งœ๋ฆฌ ๋ฆฌ์‚ฌ์ดํด๋Ÿฌ๋ทฐ๊ฐ€ ์™„์„ฑ๋๋‹ค. RecycerView๋ž‘ ๋™์ž‘๋ฐฉ์‹์ด ์ข€ ์ฐจ์ด๊ฐ€ ์žˆ๋Š”๋ฐ, RecyclerView๊ฐ€ ์•„์ดํ…œ ๋ทฐ๋ฅผ ๊ณ„์† ์žฌํ™œ์šฉํ•˜๋Š” ๋ฐ˜๋ฉด Lazy ์‹œ๋ฆฌ์ฆˆ๋“ค์€ ๊ทธ๋ƒฅ ์ƒˆ ์•„์ดํ…œ ์ปดํฌ์ €๋ธ”์„ emitํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค. ์•„์ดํ…œ์˜ ์ƒํƒœ๋„ ๊ณ ์ •ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ธ๋ฑ์Šค์™€ ํ•ด๋‹น ์•„์ดํ…œ์˜ ์ƒํƒœ๊ฐ’์„ rememberSaveable๋กœ ๋งŒ๋“ค์–ด์„œ ๊ด€๋ฆฌํ•˜๋ฉด ๋œ๋‹ค.

 

์•„๊นŒ key๋ฅผ ์—ฌ๊ธฐ์— ์ ์šฉํ•˜๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ์“ธ ์ˆ˜ ์žˆ๋‹ค.

@Composable
fun UserList(users: List<User>) {
    LazyColumn {
        items(users, key = { user -> user.id }) { user ->
            UserItem(user)
        }
    }
}

๊ฐ ํ•ญ๋ชฉ์˜ ๊ณ ์œ  ํ‚ค๋ฅผ user์˜ id๋กœ ์ง€์ •ํ•ด์ฃผ๋Š” ์ฝ”๋“œ๋‹ค.

@Composable
fun LazyColumnTest(items: List<String>) {
    LazyColumn(
        contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
        verticalArrangement = Arrangement.spacedBy(4.dp)
    ) {
        item {
            Text("Header")
        }
        
        items(items) { item ->
            Text(text = item)
        }
        
        itemsIndexed(items) { index, item ->
            Text("Item at $index is $item")
        }
        
        item {
            Text("Footer")
        }
    }
}

items์— ์•„์ดํ…œ๋“ค์ด ๋“ค์–ด๊ฐ€๊ณ , ๊ทธ์ „์— ์žˆ๋Š” item์€ ํ—ค๋”, ๊ทธ ๋‹ค์Œ์— ์žˆ๋Š” item์€ ํ‘ธํ„ฐ๋‹ค. ์‹ฌ์ง€์–ด items๋ฅผ ์—ฌ๋Ÿฌ๊ฐœ ๋„ฃ์œผ๋ฉด ์ž๋™์œผ๋กœ concat recycerview๋‹ค. itemsIndexed๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ธ๋ฑ์Šค๋ฒˆํ˜ธ์™€ ๊ฐ™์ด ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์–ด์„œ ์กฐ๊ธˆ ๋” ๊ฐœ๋ฐœ ์นœํ™”์ ์ด๋‹ค. 

์ข€ ๋” ์ถฉ๊ฒฉ์ ์ธ๊ฑด, LazyListState๋ฅผ ์จ์„œ list state๋ฅผ ๋ฐ›๊ณ , ๊ทธ๊ฑธ ๊ธฐ์ค€์œผ๋กœ ํ„ฐ๋ฌด๋‹ˆ ์—†์ด ์‰ฝ๊ฒŒ ๋ฌดํ•œ์Šคํฌ๋กค์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด๋‹ค.

val listState = rememberLazyListState()

LazyColumn(state = listState) {
	...
}
LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .collect { index ->
            if (index > items.size - 10) {
                items += List(20) { "์ƒˆ ์•„์ดํ…œ ${items.size + it}" }
            }
        }
}

์ด๋ ‡๊ฒŒ ๋ฌดํ•œ์Šคํฌ๋กค ๊ตฌํ˜„์ด ๋๋‚ฌ๋‹ค. ์ง€๊ธˆ์€ ๊ทธ๋ƒฅ ํ•˜๋“œ์ฝ”๋”ฉ์œผ๋กœ ์•„์ดํ…œ์„ ๋Š˜๋ ค์คฌ์ง€๋งŒ, ๋žŒ๋‹ค๋กœ ์ƒ์œ„ ์ปดํฌ์ €๋ธ”์—์„œ ์•„์ดํ…œ๋“ค์„ ๋ฐ›์•„์˜ค๋Š” ํ˜•ํƒœ๊ฐ€ ์ž์ฃผ ์“ฐ์ธ๋‹ค๊ณ  ํ•œ๋‹ค.

 

๋„์›€์ด ๋๋‹ค๋ฉด ๋Œ“๊ธ€์ด๋‚˜ ๊ณต๊ฐ ๋ฒ„ํŠผ ํ•œ ๋ฒˆ์”ฉ ๋ˆ„๋ฅด๊ณ  ๊ฐ€์ฃผ์„ธ์š”!

 

๋ฐ˜์‘ํ˜•

'Android ๐Ÿ–ฅ๏ธ > Compose๐Ÿ“–' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

@Immutable  (0) 2025.02.23
SideEffect, LaunchedEffect, DisposableEffect ๋™์ž‘์›๋ฆฌ  (1) 2024.12.14
Composition & Recomposition ๋™์ž‘ ์›๋ฆฌ  (1) 2024.12.14
COMMENT