๊นจ์•Œ ๊ฐœ๋… ๐Ÿ“‘/Android

[Android] Jetpack Compose - State Remembering

interfacer_han 2024. 7. 23. 00:17

#1 ๊ฐœ์š”

 

์ƒํƒœ ๋ฐ Jetpack Compose  |  Android Developers

์ด ํŽ˜์ด์ง€๋Š” Cloud Translation API๋ฅผ ํ†ตํ•ด ๋ฒˆ์—ญ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ƒํƒœ ๋ฐ Jetpack Compose ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์ •๋ฆฌํ•˜๊ธฐ ๋‚ด ํ™˜๊ฒฝ์„ค์ •์„ ๊ธฐ์ค€์œผ๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•˜์„ธ์š”. ์•ฑ์˜ ์ƒํƒœ๋Š” ์‹œ๊ฐ„์ด ์ง€๋‚จ์— ๋”ฐ๋ผ

developer.android.com

State๊ฐ€ ๊ฐ€์ง€๋Š” ์œ„์น˜ ์ œ์•ฝ ๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น ์ œ์•ฝ์„ ํ•ด์†Œํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์‚ดํŽด๋ณธ๋‹ค.
 

#2 ์ฝ”๋“œ

#2-1 ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ฝ”๋“œ (State object์˜ ์œ„์น˜ ์ œ์•ฝ)

@Composable
fun ButtonExample(modifierParam: Modifier = Modifier) {
    val count = mutableStateOf(0)

    Button(
        onClick = {
            Log.i("interfacer_han", "Current count value: ${++count.value}")
        }, modifier = modifierParam
    ) {
        Text(
            text = "Count: ${count.value}", fontSize = 40.sp
        )
    }
}

State๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” Composable์ด๋‹ค. ํ•˜์ง€๋งŒ ์ด ์ฝ”๋“œ๋Š” "Creating a state object during composition without using remember" ๋ผ๋Š” ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฑ‰์–ด๋‚ธ๋‹ค. 'remember'๊ฐ€ ๋ฌด์—‡์ธ์ง€๋Š” ์•„๋ž˜์—์„œ ์„ค๋ช…ํ•˜๊ณ , ๋จผ์ € ์™œ ์ด๋Ÿฐ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋‚˜์™”๋Š” ์ง€๋ฅผ ์•Œ์•„์•ผ ํ•œ๋‹ค. State์— ๋Œ€ํ•ด ๋‹ค๋ฃฌ ์ด ๊ฒŒ์‹œ๊ธ€์—์„œ ๋ณด๋“ฏ, State๊ฐ€ ๋ณ€ํ•˜๋ฉด ํ•ด๋‹น State์— ์—ฐ๊ด€๋œ Composable์„ ๋‹ค์‹œ ๋กœ๋“œ(์žฌ๊ตฌ์„ฑ, recomposition)ํ•œ๋‹ค. Composable์ด ๋‹ค์‹œ ๋กœ๋“œ๋˜๋ฉด ๊ทธ ์†์˜ State๋„ ๋‹ค์‹œ ์ดˆ๊ธฐํ™”๋œ๋‹ค. ๋ฌดํ•œ ๋ฃจํ”„์— ๋น ์ง€๊ณ  ๋งŒ๋‹ค. ์ฆ‰, State๋Š” Composable ๋‚ด๋ถ€์— ์œ„์น˜ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์ผ์ข…์˜ ์œ„์น˜ ์ œ์•ฝ์„ ๊ฐ€์ง„๋‹ค.
 
์—ฌ๋‹ด์œผ๋กœ, ์• ์ดˆ์— count๋ฅผ Composable์—์„œ ๋นผ ์ „์—ญ ๋ณ€์ˆ˜๋กœ ๋‘๋ฉด ๊ทธ๋งŒ์ด์ง€ ์•Š๋ƒ๋Š” ์ƒ๊ฐ๋„ ๋“ค ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ, ์ด๋Š” Jetpack Compose์—์„œ ๊ถŒ์žฅ๋˜์ง€ ์•Š๋Š” ๋ฐฉ์‹์ด๋‹ค. ์™œ๋ƒํ•˜๋ฉด Composable๋“ค์€ ๋…๋ฆฝ์ ์œผ๋กœ ์žฌ๊ตฌ์„ฑ(recomposition)๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์œ„์˜ ์ฝ”๋“œ์—์„œ๋Š” ButtonExample() ๋‹จ ํ•˜๋‚˜์˜ Composable๋งŒ ์กด์žฌํ•˜์ง€๋งŒ, ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ˆ˜ ๋งŽ์€ Composable์ด ์กด์žฌํ•  ๊ฒƒ์ด๋‹ค. ๋”ฐ๋ผ์„œ ๊ทธ ์ƒํ™ฉ์—์„œ ์ „์—ญ ๋ณ€์ˆ˜๋กœ State๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋‚˜์ค‘์— (UI ์œ ์ง€๋ณด์ˆ˜ ๋ฉด์—์„œ) ๊ฐ๋‹นํ•˜๊ธฐ ํž˜๋“  ์ฝ”๋“œ๊ฐ€ ๋งŒ๋“ค์–ด์งˆ ์—ฌ์ง€๋ฅผ ๋‚ณ๊ฒŒ ๋œ๋‹ค. ํ•˜์ง€๋งŒ State๊ฐ€ ์•„๋ž˜์— ๊ณง ๋‚˜์˜ฌ ํ•ด๊ฒฐ๋ฒ•์— ์˜ํ•ด Composable์— ์—๋Ÿฌ ์—†์ด ๋“ค์–ด๊ฐˆ ์ˆ˜์žˆ๊ฒŒ ๋œ๋‹ค๊ณ  ํ•ด๋„ ์ด ๋˜ํ•œ ์—ฌ์ „ํžˆ ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์–ด๋ ค์šด ์ฝ”๋“œ๋‹ค. ๊ทธ ์ด์œ ๋Š” ์ด์–ด์ง€๋Š” ๊ฒŒ์‹œ๊ธ€(State Hoisting) ์ฐธ์กฐ.

 

#2-2 remember๋ฅผ ํ†ตํ•œ ํ•ด๊ฒฐ (State object์˜ ์œ„์น˜ ์ œ์•ฝ ํ•ด์†Œ)

@Composable
fun ButtonExample(modifierParam: Modifier = Modifier) {
    val count = remember {
        mutableStateOf(0)
    }

    Button(
        onClick = {
            Log.i("interfacer_han", "Current count value: ${++count.value}")
        }, modifier = modifierParam
    ) {
        Text(
            text = "Count: ${count.value}", fontSize = 40.sp
        )
    }
}

ํ•ด๊ฒฐ๋ฒ•์€ #2-1์˜ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์—์„œ ๋ณด๋“ฏ, ๊ทธ๋ƒฅ remember { ... }๋กœ State๋ฅผ ๊ฐ์‹ธ๋ฒ„๋ฆฌ๋ฉด ๋œ๋‹ค. remember๋Š” Composable ๋‚ด์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜์ธ๋ฐ, State๋ฅผ ๋ณ„๋„ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅํ•จ์œผ๋กœ์จ, recomposition์ด ์ผ์–ด๋‚˜๋„ ๊ทธ ๊ฐ’์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š๊ฒŒ ๋งŒ๋“ ๋‹ค. ํ•œ ๋งˆ๋””๋กœ remember๋Š” ์žฌ๊ตฌ์„ฑ(recomposition)์— ๋Œ€ํ•œ ๋ณดํ˜ธ๋ง‰์ด๋‹ค.


๊ธฐ์–ตํ•ด์•ผํ•  ๊ฒƒ์€ remember { ... }๋กœ ๊ฐ์‹ธ๋Š” ๋Œ€์ƒ์ด State ํ”„๋กœํผํ‹ฐ๊ฐ€ ์•„๋‹ˆ๋ผ, State ๊ฐ์ฒด(Object)๋ผ๋Š” ๋‹น์—ฐํ•œ ์‚ฌ์‹ค์ด๋‹ค. ์ฆ‰, ํ”„๋กœํผํ‹ฐ์ธ count๊ฐ€ ์•„๋‹ˆ๋ผ ์ธ์Šคํ„ด์Šค์ธ mutableStateOf(0)๋ฅผ remember { ... }๋กœ ๊ฐ์‹ผ๋‹ค. State ํ”„๋กœํผํ‹ฐ๋Š” Jetpack Compose ๋Ÿฐํƒ€์ž„์— ์˜ํ•ด ์–ด์ฐจํ”ผ ํ•ญ์ƒ ๊ทธ value๊ฐ’์ด ๊ธฐ์–ต๋˜๊ณ  ๋ชจ๋‹ˆํ„ฐ๋ง๋œ๋‹ค. ๊ทธ๋ž˜์•ผ State ํ”„๋กœํผํ‹ฐ์˜ value ์†์„ฑ์ด ๋ณ€ํ•  ๋•Œ, ์•”์‹œ์ ์œผ๋กœ Recomposition์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ State object๋Š” ๊ทธ๋ ‡์ง€ ์•Š๋‹ค. ๋”ฐ๋ผ์„œ ๋ช…์‹œ์ ์œผ๋กœ remember { ... }๋กœ ๊ฐ์‹ธ๋Š” ์ฒ˜๋ฆฌ๊ฐ€ ์š”๊ตฌ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

 

#2-3 rememberSaveable

@Composable
fun ButtonExample(modifierParam: Modifier = Modifier) {
    val count = rememberSaveable {
        mutableStateOf(0)
    }

    Button(
        onClick = {
            Log.i("interfacer_han", "Current count value: ${++count.value}")
        }, modifier = modifierParam
    ) {
        Text(
            text = "Count: ${count.value}", fontSize = 40.sp
        )
    }
}

rememberSaveable์€ remember์˜ ์‹ฌํ™”ํŒ์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค. remember๋Š” recomposition์€ ๋ง‰์•„๋‚ด์ง€๋งŒ ํ™”๋ฉด ํšŒ์ „ ๋“ฑ์œผ๋กœ onCreate()๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋˜๋Š” ๊ฒฝ์šฐ์—๋Š” State์˜ ์ดˆ๊ธฐํ™”๋ฅผ ๋ง‰์„ ์ˆ˜ ์—†๋‹ค. ๋‹ค์‹œ ๋งํ•˜์ž๋ฉด, View ์ž์ฒด๊ฐ€ ํŒŒ๊ดด๋˜๋Š” ๊ฒฝ์šฐ๋Š” ๋ง‰์„ ์ˆ˜ ์—†๋‹ค. rememberSaveable์€ ์ด ๊ฒฝ์šฐ(View์˜ ํŒŒ๊ดด ๋ฐ ์žฌ์ƒ์„ฑ)์—๋„ State๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š๊ฒŒ ๋ฐฉ์–ดํ•œ๋‹ค. ํ•˜์ง€๋งŒ, ๋‚จ๋ฐœํ•ด์„œ๋Š” ์•ˆ ๋œ๋‹ค. ์งง์€ ์ฝ”๋“œ๋ผ๋ฉด ๋ชจ๋ฅผ๊นŒ, ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์งˆ ๋• ๊ทธ๋ƒฅ ViewModel์„ ์“ฐ๋Š” ๊ฒŒ ์ตœ์ ์˜ ๋ฐฉ์‹์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
 

#3 ์ž‘๋™ ํ™•์ธ (rememberSaveable์„ ์‚ฌ์šฉํ•œ ๊ฒฝ์šฐ)

rememberSaveable๊ฐ€ ์•„๋‹ˆ๋ผ remember๋ฅผ ์ผ๋‹ค๋ฉด, ํ™”๋ฉด ํšŒ์ „ ์ „๊นŒ์ง€๋Š” ์ž˜ ๋™์ž‘ํ•˜๊ฒ ์ง€๋งŒ, ํ™”๋ฉด ํšŒ์ „์ด ๋˜๊ณ ๋‚˜์„œ๋Š” count.value๊ฐ€ 0์œผ๋กœ ์ดˆ๊ธฐํ™”๋œ๋‹ค.
 

#4 ์š”์•ฝ

remember๋Š” ์žฌ๊ตฌ์„ฑ(recomposition)์— ๋Œ€ํ•œ ๋ณดํ˜ธ๋ง‰์ด๋‹ค.
 

#5 ์™„์„ฑ๋œ ์•ฑ

 

android-practice/jetpack-compose/StateRemembering at master ยท Kanmanemone/android-practice

Contribute to Kanmanemone/android-practice development by creating an account on GitHub.

github.com