remember는 Jetpack Compose의 함수로, 재구성 간에 값을 유지하여 Composable 함수가 다시 시작될 때마다 상태가 재설정되는 것을 방지합니다. remember가 없으면 함수 내부에 선언된 모든 변수가 UI가 업데이트될 때마다 재설정되어 상태가 불안정하고 쓸모없게 됩니다. Google Android Developers, 2026에 따르면, remember의 올바른 사용은 선언형 UI에서 올바른 상태 관리의 기초입니다.
핵심 포인트
remember는 androidx.compose.runtime 패키지의 Jetpack Compose 내장 함수로, Composable 함수의 연속적인 호출 간에 값을 유지하는 객체를 생성합니다. 기술적으로 remember는 슬롯(UI 트리의 각 함수 호출 위치에 할당된 특수 메모리 셀)과 함께 작동합니다. 함수가 컴포지션 내에 있는 한(if 조건으로 제거되지 않는 한), remember는 각 재구성 시 저장된 값을 반환합니다.
remember의 문법은 간단합니다. 중괄호 안에 초기 값의 계산 블록을 지정합니다. 블록은 함수의 첫 번째 컴포지션 중 한 번만 실행됩니다. 이후의 모든 재구성은 블록을 다시 실행하지 않고 이미 저장된 값을 반환합니다. 그러나 키 매개변수가 지정된 경우, 키가 변경될 때 블록이 다시 실행되어 입력 데이터를 기반으로 값을 다시 계산할 수 있습니다.
remember는 Kotlin의 마법이 아닙니다. 키 매개변수와 계산 람다가 있는 간단한 함수입니다. 내부 구현은 Composer를 사용하여 슬롯을 읽고 씁니다. Kotlin 컴파일러는 remember 호출을 보고 현재 CompositionContext에 접근하여 슬롯을 관리하는 코드를 생성합니다. 즉, remember는 Composable 함수 내에서만 또는 컴포지션 컨텍스트 내의 다른 함수에서만 호출할 수 있습니다.
Jetpack Compose에는 remember의 세 가지 주요 변형이 있으며, 각각 고유한 목적이 있습니다. 기본 remember는 현재 프로세스 메모리에만 값을 유지합니다. 화면 회전(설정 변경) 시 Compose는 컴포지션을 다시 생성하고 모든 remember 값이 재설정됩니다. 설정 변경 및 프로세스 중단 중에 데이터를 유지하려면 rememberSaveable을 사용합니다.
rememberSaveable은 SavedStateHandle 또는 Parcelable 메커니즘을 통해 값을 Bundle에 직렬화합니다. 이를 통해 화면 회전, "활동 유지 안 함" 설정, 일시적인 앱 최소화에서도 값을 유지할 수 있습니다. 그러나 rememberSaveable은 저장할 수 있는 데이터 유형에 제한을 둡니다. 기본형, Parcelable, Serializable이어야 하거나 Saver 변환기를 지원해야 합니다.
derivedStateOf는 유지 메커니즘이 아니라 최적화입니다. 다른 State 객체에서 값을 계산하는 State를 만듭니다. derivedStateOf는 소스 상태의 변경에 반응하지만 구독자가 있을 때만 값을 다시 계산합니다. 현재 재구성이 derivedStateOf를 읽지 않으면 계산이 수행되지 않아 빈번하지만 불필요한 업데이트에서 리소스를 절약합니다.
@Composable
fun RememberVariants() {
// 1. 기본 remember: 컴포지션을 떠날 때까지 유지
val createdAt = remember { System.currentTimeMillis() }
// 2. rememberSaveable: 화면 회전에서 생존
var username by rememberSaveable { mutableStateOf("") }
// 3. derivedStateOf: 필요할 때만 계산
val isButtonVisible = remember {
derivedStateOf { username.length() > 3 }
}
Text("생성됨: $createdAt")
TextField(value = username, onValueChange = { username = it })
if (isButtonVisible.value) {
Text("버튼이 표시됩니다")
}
}
remember와 rememberSaveable의 선택은 설정 변경 중 데이터 유지가 얼마나 중요한지에 따라 달라집니다. 회전 시 손실되어도 괜찮은 임시 상태(애니메이션, 현재 스크롤 위치, 포커스 상태)에는 일반 remember로 충분합니다. 중요한 데이터(양식 텍스트, 선택된 항목, 체크박스)에는 rememberSaveable을 사용하세요.
성능: rememberSaveable은 일반 remember보다 느립니다. Bundle에 직렬화가 필요하기 때문입니다. rememberSaveable은 정말로 Activity 재생성을 견뎌야 하는 데이터에만 사용하세요. 그 외에는 단순 remember를 사용하세요. rememberSaveable의 과도한 사용은 화면 회전 및 앱 전환 시 속도 저하를 유발합니다.
Parcelable 또는 Serializable을 지원하지 않는 클래스로 작업하는 경우 Saver를 사용하세요. 객체를 저장하고 복원하는 방법을 정의하는 변환기입니다. Saver는 save(객체를 저장 가능한 유형으로 변환)와 restore(저장된 데이터에서 객체 복원)의 람다 쌍으로 설명됩니다. mutableStateListOf 및 mutableStateMapOf에는 표준 Saver가 이미 구현되어 있습니다.
| 특성 | remember | rememberSaveable |
|---|---|---|
| 재구성 시 유지 | 예 | 예 |
| 회전 시 유지 | 아니요 | 예 |
| 프로세스 중단 시 유지 | 아니요 | 예 |
| 유형 요구사항 | 모두 | Parcelable, Serializable, Saver |
| 성능 | 높음 | 중간 |
일반적인 시나리오를 생각해 보겠습니다. 프로필 편집 화면에서 remember를 여러 목적으로 사용합니다. 양식 필드 상태 저장, 파생 값 계산, 계산 비용이 많이 드는 작업 캐싱입니다.
data class ProfileUiState(
val name: String = "",
val bio: String = "",
val isSaving: Boolean = false
)
@Composable
fun ProfileEditor() {
var state by rememberSaveable { mutableStateOf(ProfileUiState()) }
val isValid = remember {
derivedStateOf { state.name.isNotBlank() && state.bio.length() <= 500 }
}
val bioWarning = remember(state.bio) {
if (state.bio.length() > 400) {
"${500 - state.bio.length} chars left"
} else null
}
Column(modifier = Modifier.padding(16.dp)) {
OutlinedTextField(
value = state.name,
onValueChange = { state = state.copy(name = it) },
label = { Text("이름") }
)
OutlinedTextField(
value = state.bio,
onValueChange = { state = state.copy(bio = it) },
label = { Text("정보") }
)
bioWarning?.let { Text(it, color = MaterialTheme.colorScheme.error) }
Button(onClick = { /* save */ },
enabled = isValid.value) {
Text("저장")
}
}
}
예제에서 상태는 rememberSaveable을 통해 저장됩니다. 화면 회전 시 텍스트가 손실되지 않습니다. isValid는 derivedStateOf를 통해 계산되어 재구성 중 불필요한 계산을 방지합니다. bioWarning은 bio 키와 함께 remember를 사용합니다. 이는 비용이 많이 드는 계산(데모용, 선택 사항)으로, 모든 재구성이 아닌 bio가 변경될 때만 다시 계산됩니다.
파생 상태는 다른 State 객체에서 계산된 값입니다. 매 재구성마다 계산하여 동일한 결과에 CPU를 낭비하는 대신, remember와 derivedStateOf는 소스가 변경될 때만 값을 계산합니다. 이는 필터링, 정렬 및 데이터 집계에 특히 유용합니다.
키가 있는 remember(remember(key) { calculation })는 또 다른 최적화 메커니즘입니다. 마지막 재구성 이후 키가 변경되지 않았다면 계산 블록이 실행되지 않고 캐시된 값이 반환됩니다. 이는 생성 비용이 많이 드는 객체(날짜 형식 지정, JSON 파싱, 큰 불변 컬렉션 생성)를 캐싱하는 데 편리합니다.
@Composable
fun SearchResults(allItems: List<Item>, query: String) {
// derivedStateOf: 입력이 변경될 때만 필터 재계산
val filtered = remember {
derivedStateOf {
allItems.filter { it.title.contains(query, true) }
}
}
// 키가 있는 remember: 형식화된 통계는 쿼리 변경 시에만 재계산
val statsText = remember(query) {
"Results for query \"$query\": ${filtered.value.size}"
}
Text(statsText)
LazyColumn {
items(filtered.value, key = { it.id }) { item ->
Text(item.title)
}
}
}
filtered는 derivedStateOf로, allItems 또는 query가 변경될 때 자동으로 다시 계산됩니다. statsText는 remember(query)를 사용합니다. 비용이 많이 드는 문자열 형식 지정은 검색 쿼리가 변경될 때만 수행됩니다. derivedStateOf와 키가 있는 remember의 조합은 최대 성능을 제공합니다. 파생 상태는 필요할 때만 계산되고 복잡한 객체는 키가 변경될 때까지 캐시됩니다.
자주 묻는 질문
아니요, remember는 compose.runtime 패키지의 함수로 CompositionContext가 필요합니다. @Composable 함수 내부에서만 또는 Composable에서 호출된 다른 함수 내부에서만 호출할 수 있습니다. 컴포지션 외부에서 데이터를 저장하려면 ViewModel을 사용하세요.
키가 없으면 remember는 계산 블록을 첫 번째 컴포지션 중 한 번만 실행합니다. 이후의 모든 재구성은 저장된 값을 반환합니다. 데이터가 변경될 때 값을 다시 계산해야 하는 경우 반드시 키로 지정하세요: remember(data) { compute(data) }.
remember를 직접 리셋하는 방법은 없습니다. 유일한 방법은 함수를 컴포지션에서 제거하고(예: if 조건으로 숨기기) 다시 표시하는 것입니다. 다시 진입하면 remember 블록이 다시 실행되어 새로운 초기 값을 만듭니다.
remember는 Composable 함수 슬롯에 상태를 저장하고 함수가 컴포지션에 있는 동안만 유지됩니다. ViewModel은 화면의 생명 주기가 지속되는 동안 유지됩니다. ViewModel은 회전 시 유지되며 비즈니스 로직에 사용됩니다. remember는 단일 함수 외부에서는 필요하지 않은 로컬 UI 상태를 위한 것입니다.
예, remember는 @Preview Composable 함수에서 올바르게 작동합니다. Preview가 완전한 CompositionContext를 생성하기 때문입니다. 그러나 rememberSaveable은 Preview에서 올바르게 작동하지 않을 수 있습니다. 미리보기 환경에서 SavedStateHandle이 없을 수 있기 때문입니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.