Notice
Recent Posts
Recent Comments
«   2026/07   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

훌륭한 개발자가 되기 위하여

[Jetpack Compose] State와 Remember란? 본문

안드로이드

[Jetpack Compose] State와 Remember란?

jay20033 2025. 8. 6. 14:33

state란?

UI에서 동적으로 반영되는 데이터, 변경될 수 있는 값이다.

Compose에서 State는 값이 바뀔 때 해당 값을 읽은 컴포저블의 recomposition을 예약하는 관찰 가능한 값이다.

  • 네트워크 연결을 설정할 수 없을 때 표시되는 Snackbar 메시지 노출
  • 블로그 게시물 및 관련 댓글
  • 사용자가 클릭하면 버튼에서 재생되는 애니메이션

Composable은 선언적이므로 UI 업데이트는 컴포저블이 변경된 매개변수를 통해 호출될 때만 발생한다.

초기 Composition : 처음 컴포저블을 실행하여 생성된 컴포지션

Recomposition : 데이터가 변경될 때 (상태 변경 시) 발생되며, 관련 컴포저블을 업데이트하여 새로운 상태를 반영한다.

Compose Runtime은 상태 변경 사항을 자동으로 추적하고, 안드로이드 View 시스템에서 UI를 호출하기 위해 사용하는 View.invalidate() 메서드와 유사한 동작을 개발자 대신하여 UI를 업데이트한다.

@Composable
fun HelloContent() {
    Column(modifier = Modifier.padding(16.dp)) {
        var name by remember { mutableStateOf("") }
        if (name.isNotEmpty()) {
            Text(
                text = "Hello, $name!",
                modifier = Modifier.padding(bottom = 8.dp),
                style = MaterialTheme.typography.bodyMedium
            )
        }
        OutlinedTextField(
            value = name,
            onValueChange = { name = it },
            label = { Text("Name") }
        )
    }
}

remember

초기 컴포지션 발생시 메모리에 객체를 저장하고 recomposition이 발생하면 저장된 값을 반환한다.

💥구성 변경 시에는 저장된 값 날라감.

rememberSaveable

recomposition 뿐만 아니라, 화면 회전과 같은 구성 변경 시에도 Bundle에 저장할 수 있는 값을 자동으로 저장한다.

remember 위에서 동작하며, SavedStateRegistry에 값을 저장/복원해 구성 변경 + 프로세스 재생성 이후에도 값을 살려준다.

mutablestateof

상태 값이 변경될 때 recomposition을 트리거하는 관찰 가능한 상태 객체를 생성한다.

val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }

 

일반적으로 기능만 놓고 봤을때 rememberSaveable이 구성 변경시에도 저장된 데이터가 유지되기에 rememberSaveable만 사용하면 되지 않을까라는 생각을 하였는데, 내부 구현체를 보면 rememberSaveable은 remember보다 다양한 내부 처리 및 오버헤드가 발생하기 때문에 rememberSaveable을 무턱대고 사용하게 되면 오히려 성능이 떨어질 수 있다.

사용 시기

remember : 애니메이션이나 임시적인 UI 상태와 같이 유지될 필요가 없는 일시적인 상태가 필요한 경우

rememberSaveable : 사용자 입력, 선택 상태와 같이 구성 변경 시에도 유지되어야 하는 상태가 필요한 경우

내부 구조

remember

@Composable
public inline fun <T> remember(crossinline calculation: @DisallowComposableCalls () -> T): T =
    currentComposer.cache(false, calculation)

단순히 Composer 인스턴스에서 cache 함수를 호출한다.

@ComposeCompilerApi
public inline fun <T> Composer.cache(invalid: Boolean, block: @DisallowComposableCalls () -> T): T {
    @Suppress("UNCHECKED_CAST")
    return rememberedValue().let {
        if (invalid || it === Composer.Empty) {
            val value = block()
            updateRememberedValue(value)
            value
        } else it
    } as T
}

Cache 내부 코드를 보면 처음에 값이 유효하지 않거나 초기화되 지 않았는지 확인한다.

초기화 되지 않은 경우 block 함수가 실행되어 값을 계산하고 컴포지션 데이터에 값을 저장하고 반환한다.

이전에 저장된 값이 있는 경우 값을 단순히 복원하여 반환한다.

 

rememberSaveable

@Composable
public fun <T : Any> rememberSaveable(
    vararg inputs: Any?,
    saver: Saver<T, out Any> = autoSaver(),
    key: String? = null,
    init: () -> T,
): T {
    val compositeKey = currentCompositeKeyHashCode
    // key is the one provided by the user or the one generated by the compose runtime
    val finalKey =
        if (!key.isNullOrEmpty()) {
            key
        } else {
            compositeKey.toString(MaxSupportedRadix)
        }
    @Suppress("UNCHECKED_CAST") (saver as Saver<T, Any>)

    val registry = LocalSaveableStateRegistry.current

    val holder = remember {
        // value is restored using the registry or created via [init] lambda
        val restored = registry?.consumeRestored(finalKey)?.let { saver.restore(it) }
        val finalValue = restored ?: init()
        SaveableHolder(saver, registry, finalKey, finalValue, inputs)
    }

    val value = holder.getValueIfInputsDidntChange(inputs) ?: init()
    SideEffect { holder.update(saver, registry, finalKey, value, inputs) }

    return value
}

 

키 생성

val finalKey =
    if (!key.isNullOrEmpty()) {
        key
    } else {
        compositeKey.toString(MaxSupportedRadix)
    }
public val currentCompositeKeyHashCode: CompositeKeyHashCode
    @Composable
    @ExplicitGroupsComposable
    @OptIn(InternalComposeApi::class)
    get() = currentComposer.compositeKeyHashCode

key 매개변수를 통해 사용자 커스텀 키를 생성할 수 있으며, 따로 지정하지 않은 경우 현재 컴포지션 해시를 통해 키가 자동으로 생성된다.

 

상태 복원

val registry = LocalSaveableStateRegistry.current

val holder = remember {
    // value is restored using the registry or created via [init] lambda
    val restored = registry?.consumeRestored(finalKey)?.let { saver.restore(it) }

LocalSaveableStateRegistry가 이전에 저장된 값을 검색하고 저장된 값이 존재하면 Saver를 사용하여 복원한다.

 

기본값 초기화

val finalValue = restored ?: init()

복원된 값이 없으면 init 람다가 실행되어 기본값이 초기화된다.

 

SaveableHolder

SaveableHolder(saver, registry, finalKey, finalValue, inputs)

상태, saver, 레지스트리 및 입력을 관리하기 위해 SaveableHolder가 생성된다.

 

입력 변경 처리

val value = holder.getValueIfInputsDidntChange(inputs) ?: init()
fun getValueIfInputsDidntChange(inputs: Array<out Any?>): T? {
    return if (inputs.contentEquals(this.inputs)) {
        value
    } else {
        null
    }
}

입력이 변경되면 상태가 무효화되고 값이 초기화 된다.

 

Side Effect

SideEffect { holder.update(saver, registry, finalKey, value, inputs) }

Recomposition이 발생하면 업데이트된 상태가 Registry에 저장된다.

 

참고자료

https://developer.android.com/develop/ui/compose/state?hl=ko

https://medium.com/@appdevinsights/jetpack-compose-remember-vs-remembersaveable-39cb4fb6b9b9

https://velog.io/@jayjazy/Android-Compose%EC%9D%98-%EC%83%81%ED%83%9C-feat.-remember-rememberSavable

 

 

'안드로이드' 카테고리의 다른 글

[JetpackCompose] Compose Side Effect  (0) 2025.08.14
[Jetpack Compose] State Hoisting이란?  (0) 2025.08.09
Jetpack Compose의 안정성(Stability)  (0) 2025.07.27
Jetpack Compose의 동작 구조  (0) 2025.07.26
네트워크 상태 읽기  (0) 2025.02.20