Stability์— ๋Œ€ํ•œ ์ดํ•ด๋ฅผ ํ†ตํ•ด Jetpack Compose์—์„œ Recomposition ์ตœ์ ํ™”ํ•˜๊ธฐ

sana
  • #Android
  • #Jetpack Compose
  • #Compose
  • #Recomposition
  • #Stable

๋“ค์–ด๊ฐ€๋ฉฐ

์•ˆ๋…•ํ•˜์„ธ์š”. ์›๋”์›” ๋ชจ๋ฐ”์ผํŒ€ ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์ž ์˜ค์ƒ์•„์ž…๋‹ˆ๋‹ค.

Jetpack Compose๊ฐ€ ์ •์‹ ๋ฒ„์ „์œผ๋กœ ์ถœ์‹œ๋œ์ง€๋„ ๋ฒŒ์จ 2๋…„์ด ์ง€๋‚ฌ๋Š”๋ฐ์š”. ์ €ํฌํŒ€์—์„œ๋Š” ์กฐ๊ธˆ ๋Šฆ๊ธดํ–ˆ์ง€๋งŒ, ์ง€๋‚œ 6์›”๋ถ€ํ„ฐ ํ”„๋กœ์ ํŠธ์— Jetpack Compose๋ฅผ ๋„์ž…ํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€์žฅ ์ฒ˜์Œ Compose๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋งŒ๋“  ํ™”๋ฉด์€ fromm์•ฑ์˜ ๊ด€์‹ฌ์•„ํ‹ฐ ์„ ํƒ ํ™”๋ฉด์ด์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด ํ™”๋ฉด์„ ๊ฐœ๋ฐœํ–ˆ์—ˆ์„ ๋‹น์‹œ์—๋Š” Compose์— ๋Œ€ํ•œ ๊นŠ์ด ์žˆ๋Š” ์ดํ•ด๊ฐ€ ์„ ํ–‰๋˜์ง€ ์•Š์•˜๋˜ ํ„ฐ๋ผ ์˜ˆ์ƒํ–ˆ๋˜ ๊ฒƒ๊ณผ ๋‹ฌ๋ฆฌ ์žฌ๊ตฌ์„ฑ(Recomposition)์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ์ผ์–ด๋‚˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ์ดˆ๊ธฐ์˜ ๊ด€์‹ฌ์•„ํ‹ฐ ํ™”๋ฉด๊ณผ ์ฝ”๋“œ๋“ค์„ ์‚ดํŽด๋ณด๋ฉฐ ์™œ ์žฌ๊ตฌ์„ฑ์ด ๋ฐœ์ƒํ–ˆ๋Š”์ง€, ์–ด๋–ป๊ฒŒ ๋ถˆํ•„์š”ํ•œ ์žฌ๊ตฌ์„ฑ์„ ์ค„์—ฌ๋‚˜๊ฐ”๋Š”์ง€ ์‚ดํŽด๋ณด๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ ์žฌ๊ตฌ์„ฑ์ด ๋™์ž‘ํ•˜๋Š” ๋ฐ ์žˆ์–ด ํ•ต์‹ฌ๊ฐœ๋…์ธ Stability(์•ˆ์ •์„ฑ)์„ ์ค‘์‹ฌ์œผ๋กœ Compose์˜ ์žฌ๊ตฌ์„ฑ์„ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ปดํฌ์ €๋ธ”์˜ ์žฌ๊ตฌ์„ฑ์„ ์ตœ์ ํ™”ํ•˜์—ฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ๋ฐ ๋„์›€์„ ์–ป์„ ์ˆ˜ ์žˆ๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.


๋ถˆํ•„์š”ํ•œ ์žฌ๊ตฌ์„ฑ ๋ฐœ์ƒ ์‚ฌ๋ก€

Icon

๊ด€์‹ฌ์•„ํ‹ฐ ์„ ํƒ ํ™”๋ฉด์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ”„๋กฌ์— ์ž…์ ํ•ด ์žˆ๋Š” ์•„ํ‹ฐ๋“ค์„ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ๋Š” SearchTextField ๋ผ๋Š” ์ปดํฌ์ €๋ธ”์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ปดํฌ์ €๋ธ”์€ query๋ผ๋Š” ์ƒํƒœ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.


@Composable
fun SearchTextField() {
    val (query, setText) = remember { mutableStateOf("") }

    BasicTextField(
        value = query,
        onValueChange = { setText(it) },
        decorationBox = { innerTextField ->
            Box(
            ) {
                if (query.isEmpty()) {
                    Text(text = "์•„ํ‹ฐ์ŠคํŠธ๋ช… ๊ฒ€์ƒ‰")
                }
                Row(
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    Icon(
                        painter = painterResource(id = R.drawable.baseline_search_24),
                        contentDescription = "๊ฒ€์ƒ‰ ์•„์ด์ฝ˜",
                    )
                    Box(
                    ) {
                        innerTextField()
                    }
                    if (query.isNotEmpty()) {
                        Icon(
                            painter = painterResource(id = R.drawable.baseline_search_24),
                            contentDescription = "์ง€์šฐ๊ธฐ ์•„์ด์ฝ˜",
                            modifier = Modifier.clickable { setText("") }
                        )
                    }
                }
            }
        }
    )
}

๊ทธ๋Ÿฐ๋ฐ Layout Inspector๋ฅผ ํ†ตํ•ด ์‚ดํŽด๋ณด๋‹ˆ, SearchTextField์— ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•  ๋•Œ๋งˆ๋‹ค ๊ฒ€์ƒ‰ ์•„์ด์ฝ˜๊ณผ ์ง€์šฐ๊ธฐ ์•„์ด์ฝ˜์ด ๋ชจ๋‘ ์žฌ๊ตฌ์„ฑ๋œ๋‹ค๋Š” ์‚ฌ์‹ค์„ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค.

์œ ์ €๊ฐ€ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜์—ฌ query๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์œผ๋ฏ€๋กœ ์žฌ๊ตฌ์„ฑ์ด ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ธ๋ฐ์š”. ๊ทธ๋Ÿฐ๋ฐ ์™œ ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ์„ ์žฌ๊ตฌ์„ฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๊ฑด๋„ˆ๋›ฐ์–ด์•ผ ํ•  Icon๋„ ํ•ญ์ƒ ์žฌ๊ตฌ์„ฑ๋˜๋Š” ๊ฒƒ์ผ๊นŒ์š”?

๋‹ต์€ Painter ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๊ณต์‹๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด, Painter ํด๋ž˜์Šค๋Š” ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ์˜ ๋ณต์žก์„ฑ์œผ๋กœ ์ธํ•ด ์•ˆ์ •์ ์ธ(stable) ํด๋ž˜์Šค๋กœ ๋ช…์‹œํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ๋‚˜์™€์žˆ์Šต๋‹ˆ๋‹ค. ์ด โ€œ์•ˆ์ •์ โ€์ด๋ผ๋Š” ์˜๋ฏธ๊ฐ€ ๋ฌด์—‡์ธ์ง€๋Š” ์ด๋”ฐ๊ฐ€ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ณ  ์—ฌ๊ธฐ์„œ๋Š” ๋‹ค์Œ ๋‘ ๊ฐ€์ง€๋งŒ ๊ธฐ์–ตํ•˜๊ณ  ๋„˜์–ด๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.

  1. ์ž…๋ ฅ์ด ์•ˆ์ •์ ์ด๊ณ  ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•„์•ผ ์žฌ๊ตฌ์„ฑ์„ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์žˆ๋‹ค.
  2. ์•ˆ์ •์ ์ธ ํƒ€์ž…์œผ๋กœ๋Š” ์›์‹œํƒ€์ž…(Int, Float, Boolean ๋“ฑ)๊ณผ ๋ฌธ์ž์—ด, ๋ชจ๋“  ํ•จ์ˆ˜ ํƒ€์ž…(๋žŒ๋‹ค)์ด ์žˆ๋‹ค.

๋”ฐ๋ผ์„œ Painter๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹  Int ํƒ€์ž…์˜ ๋ฆฌ์†Œ์Šค ์•„์ด๋””๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌํ•˜๋Š” ์ƒˆ๋กœ์šด ์ปดํฌ์ €๋ธ”์„ ๋งŒ๋“ค๋ฉด ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋ฉ๋‹ˆ๋‹ค.

@Composable
fun StableImage (
    @DrawableRes drawableResId: Int, 
    description : String,
) {
    val painter = painterResource(id = drawableResId)
    Image(
        painter = painter,
        contentDescription = description
    )
}

์ด์ œ ๊ธฐ์กด Icon ๋Œ€์‹  StableImage๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด Int ํƒ€์ž…์ธ drawableResId๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ์ด์ƒ Icon์€ ์žฌ๊ตฌ์„ฑ๋˜์ง€ ์•Š๊ณ  ๊ฑด๋„ˆ๋›ฐ๊ฒŒ ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜๋Š”, ImageVector๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ์—๋Š” query๊ฐ€ ์•„๋ฌด๋ฆฌ ๋ณ€๊ฒฝ๋˜์–ด๋„ Icon์€ ์žฌ๊ตฌ์„ฑ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค!


Button

๋˜ ๋‹ค๋ฅธ ์ผ€์ด์Šค๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ด€์‹ฌ์•„ํ‹ฐํ™”๋ฉด ์ƒ๋‹จ์—๋Š” DefaultTopBarWithLeftIconAndRightText(์ดํ•˜ DefaultTopBar)๊ฐ€ ์žˆ๋Š”๋ฐ์š”. DefaultTopBar ๋‚ด๋ถ€์—๋Š” ํ™”๋ฉด์„ ๋‹ซ์„ ์ˆ˜ ์žˆ๋Š” ํ™”์‚ดํ‘œ IconButton๊ณผ โ€˜์ €์žฅโ€™์ด๋ผ๊ณ  ์“ฐ์—ฌ์ง„ Text๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

DetafultTopBar

๊ทธ๋ฆฌ๊ณ  FavoriteScreen์€ DefaultTopBar๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉด์„œ ๊ด€์‹ฌ์•„ํ‹ฐ ํŽธ์ง‘ ์—ฌ๋ถ€๋ฅผ isEdited ๋ณ€์ˆ˜์— ์ €์žฅํ•˜์—ฌ, isEdited๊ฐ€ true๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๊ฒฝ์šฐ ๊ฐ€์žฅ ์˜ค๋ฅธ์ชฝ์˜ โ€˜์ €์žฅโ€™ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•  ์ˆ˜ ์žˆ๋„๋ก enabled ์†์„ฑ์„ ๋ณ€๊ฒฝํ•ด ์ค๋‹ˆ๋‹ค.

@Composable
fun FavoriteScreen() {

    val isEdited = remember { mutableStateOf(false) }
    
    DefaultTopBarWithLeftIconAndRightText(
        isRightTextEnabled = isEdited.value,
        onClickLeftIcon = {
            viewModel.onBackClicked()
        },
        onClickRightText = {
            viewModel.saveClicked()
        }
    )
    
}

์œ ์ €๊ฐ€ ๊ด€์‹ฌ์•„ํ‹ฐ๋ฅผ ์„ ํƒํ•˜๋ฉด isEdited ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด์„œ ์ €์žฅ๋ฒ„ํŠผ์ธ Text๊ฐ€ ์žฌ๊ตฌ์„ฑ๋˜๋Š”๋ฐ ๋ฌธ์ œ๋Š” ์ด ๋•Œ IconButton๋„ ์žฌ๊ตฌ์„ฑ์ด ์ผ์–ด๋‚œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

IconButton ์žฌ๊ตฌ์„ฑ

IconButton์€ ์˜ค์ง onClickLeftIcon์ด๋ผ๋Š” ๋žŒ๋‹ค๋งŒ์„ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›๋Š”๋ฐ์š”. ์•ž์„œ ์‚ดํŽด๋ณธ ๊ฒƒ์ฒ˜๋Ÿผ ๋žŒ๋‹ค๋Š” ์•ˆ์ •์ ์ธ ํƒ€์ž…์ด๋ฏ€๋กœ, onClickLeftIcon์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ์ด์ƒ IconButton์€ ์žฌ๊ตฌ์„ฑ๋˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์™œ ์žฌ๊ตฌ์„ฑ์ด ๋ฐœ์ƒํ• ๊นŒ์š”?

๊ทธ๊ฒƒ์€ ๋ฐ”๋กœ onClickLeftIcon ๋žŒ๋‹ค์—์„œ ๋ถˆ์•ˆ์ •ํ•œ(unstable) ViewModel ํด๋ž˜์Šค๋ฅผ ์บก์ฒ˜ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ฆ‰, ์ปดํฌ์ฆˆ ์ปดํŒŒ์ผ๋Ÿฌ ์ž…์žฅ์—์„œ๋Š” ViewModel์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š”์ง€ ํ™•์‹ ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— FavoriteScreen์ด ์žฌ๊ตฌ์„ฑ๋ ๋•Œ๋งˆ๋‹ค onClickLeftIcon ์ธ์Šคํ„ด์Šค๋„ ๋งค๋ฒˆ ์ƒˆ๋กญ๊ฒŒ ์ƒ์„ฑ์ด ๋˜์–ด ์žฌ๊ตฌ์„ฑ์ด ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ๋™์ผํ•œ onClickLeftIcon ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ๋ทฐ๋ชจ๋ธ์„ ์ฐธ์กฐํ•˜๋Š” ๋žŒ๋‹ค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹  ๋ฉ”์†Œ๋“œ ์ฐธ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•ญ์ƒ ๋™์ผํ•œ ์ฃผ์†Œ๊ฐ€ ์ „๋‹ฌ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์žฌ๊ตฌ์„ฑ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

   DefaultTopBarWithLeftIconAndRightText(
       isRightTextEnabled = isEdited.value,
       onClickLeftIcon = viewModel::onBackClicked,
       onClickRightText = viewModel::saveClicked
   )

๋˜๋Š” remember๋ฅผ ํ†ตํ•ด ๋žŒ๋‹ค ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒˆ๋กœ์šด ๋ณ€์ˆ˜๋กœ ์ €์žฅํ•˜์—ฌ ์žฌ๊ตฌ์„ฑ์ด ๋ฐœ์ƒํ•˜๋”๋ผ๋„ ์œ ์ง€๋˜๋„๋ก ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

@Composable
fun FavoriteScreen() {
    val isEdited = remember { mutableStateOf(false) }
    val onClickLeftIcon = remember { viewModel.onBackClicked() }
    val onClickRightText = remember { viewModel.saveClicked() }
 
    DefaultTopBarWithLeftIconAndRightText(
        isRightTextEnabled = isEdited.value,
        onClickLeftIcon = onClickLeftIcon,
        onClickRightText = onClickRightText
    )
    
}

๋‘ ๊ฐ€์ง€ ํ•ด๊ฒฐ์ฑ… ๋ชจ๋‘ Layout Inspector ์ƒ์—์„œ IconButton์ด ์žฌ๊ตฌ์„ฑ๋˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. IconButton ์žฌ๊ตฌ์„ฑ ์•ˆ ๋จ


Stability์— ๋Œ€ํ•œ ์ดํ•ด

์œ„์˜ ๋‘ ๊ฐ€์ง€ ์ผ€์ด์Šค๋ฅผ ํ†ตํ•ด ์ปดํฌ์ €๋ธ”์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ํƒ€์ž…์ด ๋ถˆ์•ˆ์ •ํ•˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝ๋  ๋•Œ ์žฌ๊ตฌ์„ฑ์ด ๋œ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋Œ€๋กœ ๋ชจ๋“  ์ž…๋ ฅ์ด ์•ˆ์ •์ ์ด๊ณ  ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์žฌ๊ตฌ์„ฑ์„ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ Compose ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ์žฌ๊ตฌ์„ฑ ์ค‘์— ๊ฑด๋„ˆ๋›ฐ์–ด์•ผ ํ•  ํ•จ์ˆ˜๋ฅผ ๊ฒฐ์ •ํ•˜๊ธฐ ์œ„ํ•ด ์ปดํฌ์ €๋ธ”์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ํƒ€์ž…์ด ์•ˆ์ •์ ์ธ์ง€ ์—ฌ๋ถ€๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

์•ž์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด, ๊ธฐ๋ณธ์ ์œผ๋กœ ์•ˆ์ •์ ์ธ ํƒ€์ž…์œผ๋กœ ๊ฐ„์ฃผ๋˜๋Š” ๊ฒƒ์—๋Š” ์›์‹œํƒ€์ž…, ๋ฌธ์ž์—ด, ํ•จ์ˆ˜ ํƒ€์ž… ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ ์กฐ๊ฑด์„ ๋งŒ์กฑ์‹œํ‚จ๋‹ค๋ฉด ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ํ•ด๋‹น ํด๋ž˜์Šค๋ฅผ ์•ˆ์ •์ ์ธ ํƒ€์ž…์œผ๋กœ ์ถ”๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ฐธ๊ณ 

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

๋˜๋Š” @Stable, @Immutable annotation์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐœ๋ฐœ์ž๊ฐ€ ์ž„์˜๋กœ ์–ด๋–ค ํด๋ž˜์Šค๋ฅผ ์•ˆ์ •์ ์ธ ํƒ€์ž…์œผ๋กœ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์•ˆ์ •์„ฑ ์กฐ๊ฑด์— ๋งž์ง€ ์•Š๋Š” ํด๋ž˜์Šค์— ์ด๋Ÿฌํ•œ annotation์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ถ”๋ก ํ•œ ๋‚ด์šฉ๊ณผ ๋‹ฌ๋ผ ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ฃผ์˜ํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•œ annotation์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์œ„์˜ ์กฐ๊ฑด์„ ์ง€ํ‚ค๋„๋ก ํ•˜์—ฌ ํด๋ž˜์Šค๋ฅผ ์•ˆ์ •์ ์œผ๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ๋‹ค์‹œ Icon๊ณผ Button ์‚ฌ๋ก€๋กœ ๋Œ์•„๊ฐ€ ์™œ ์žฌ๊ตฌ์„ฑ์ด ๋ฐœ์ƒํ–ˆ๋˜ ๊ฒƒ์ธ์ง€ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉ๋œ ํด๋ž˜์Šค๋“ค์„ ์‚ดํŽด๋ณด๋ฉฐ ๋ถ„์„ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ ์‚ฌ๋ก€์—์„œ Icon ์ปดํฌ์ €๋ธ”์„ ๋งŒ๋“ค ๋•Œ ImageVector๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ImageVector์˜ ๊ฒฝ์šฐ @Immutable annotation์ด ์ถ”๊ฐ€๋˜์–ด ๋ช…์‹œ์ ์œผ๋กœ stableํ•œ ํด๋ž˜์Šค์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

ImageVector

๋ฐ˜๋ฉด, Painter ํด๋ž˜์Šค์˜ ๊ฒฝ์šฐ ์•ˆ์ •์ ์ธ ํด๋ž˜์Šค๋กœ ๋ช…์‹œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์•ˆ์ •์ ์ธ ํƒ€์ž…์ด ๋˜๋ ค๋ฉด ๋‘ ์ธ์Šคํ„ด์Šค์˜ equals ๊ฒฐ๊ณผ๋ฅผ ๋น„๊ตํ•ด์•ผ ํ•˜๋Š”๋ฐ, Bitmap์˜ equals() ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐ ๋“œ๋Š” ๊ณ„์‚ฐ ๋น„์šฉ์ด ๋†’๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ Painter ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ ์˜๋„ํ•˜์ง€ ์•Š์•˜๋˜ ์žฌ๊ตฌ์„ฑ์ด ๊ณ„์† ๋ฐœ์ƒํ–ˆ๋˜ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋˜ํ•œ, ๋‘ ๋ฒˆ์งธ ์‚ฌ๋ก€์—์„œ ViewModel ํด๋ž˜์Šค ์—ญ์‹œ ์œ„์˜ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์•ˆ์ •์ ์ธ ํƒ€์ž…์ด ์•„๋‹™๋‹ˆ๋‹ค. @Stable annotaion์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๊ฐ•์ œ๋กœ ์•ˆ์ •์ ์ธ ํƒ€์ž…์œผ๋กœ ๋งŒ๋“ค์ˆ˜๋„ ์žˆ์ง€๋งŒ ๊ทธ๋ ‡๊ฒŒ ๋˜๋ฉด ์•ˆ๋“œ๋กœ์ด๋“œ ํ”„๋ ˆ์ž„์›Œํฌ(Compose)์— ๋Œ€ํ•œ ์˜์กด์„ฑ์ด ์ƒ๊ธฐ๊ธฐ ๋•Œ๋ฌธ์— ์ ์ ˆํ•œ ํ•ด๊ฒฐ์ฑ…์ด ์•„๋‹™๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ViewModel์„ ์ฐธ์กฐํ•˜๋Š” ๋Œ€์‹  ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ๊ฐ’์„ ๋„˜๊ฒจ ์žฌ๊ตฌ์„ฑ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.


๋งˆ์น˜๋ฉฐ

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” Jetpack Compose์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์žฌ๊ตฌ์„ฑ ์‚ฌ๋ก€์™€ ๊ทธ ํ•ด๊ฒฐ์ฑ…์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด Stableํ•œ ํƒ€์ž…์„ ํ™œ์šฉํ•˜์—ฌ ์žฌ๊ตฌ์„ฑ์„ ์ตœ์†Œํ™”ํ•˜๋Š” ๊ฒƒ์˜ ์ค‘์š”์„ฑ์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์•„์ง๋„ Compose ๋Œ€ํ•ด ๊ณต๋ถ€ํ•˜๊ณ  ๊ฐœ์„ ํ•  ๋ถ€๋ถ„์ด ๋งŽ์ง€๋งŒ, ์„ ์–ธํ˜• UI์˜ ์žฅ์ ๊ณผ ํ–ฅ์ƒ๋œ ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์„ ๊ฒฝํ—˜ํ•˜๋ฉฐ ์ฆ๊ฒ๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธ€์ด Compose๋ฅผ ์ดํ•ดํ•˜๋Š”๋ฐ ์กฐ๊ธˆ์ด๋‚˜๋งˆ ๋„์›€์ด ๋˜์—ˆ์œผ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค. ์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

โ† ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ

Art Changes Life

๋…ธ๋จธ์Šค์™€ ํ•จ๊ป˜ ์—”ํ„ฐํ…Œํฌ ์‚ฐ์—…์„ ํ˜์‹ ํ•ด๋‚˜๊ฐˆ ๋ฉค๋ฒ„๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.

์ฑ„์šฉ ์ค‘์ธ ๊ณต๊ณ  ๋ณด๊ธฐ