훌륭한 개발자가 되기 위하여
[Jetpack Compose] State와 Remember란? 본문
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 |