remember(keys) in Jetpack Compose: Basics & Patterns
remember(keys) in Jetpack Compose — Basics & Patterns
remember(keys)로 재구성과 캐싱을 세밀하게 제어하기 (Use remember(keys) to precisely control recomposition and caching.)
요약:
remember { … }는 처음 한 번 계산한 값을 컴포지션에 캐시하고,remember(key1, key2) { … }는 키가 바뀔 때만 캐시를 무효화해 재계산합니다. (Summary:remember { … }caches once per composition slot;remember(key1, key2) { … }invalidates and recomputes only when any key changes.)
1) API & Mental Model
시그니처와 개념 모델 (Signature and mental model)
@Composable
inline fun <T> remember(vararg keys: Any?, crossinline calculation: () -> T): T
calculation은 처음 한 번만 실행되어 값이 캐시됩니다. (Thecalculationruns once initially and is cached.)- 이후 재구성에서 키가 모두 동일하면 캐시된 값을 재사용합니다. (On subsequent recompositions, if all keys are equal, the cached value is reused.)
- 한 개라도 키가 달라지면 캐시 무효화 후
calculation을 다시 실행합니다. (If any key differs, the cache is invalidated andcalculationis recomputed.)
작은 다이어그램 (Tiny diagram)
First composition:
compute -> [cache value]
Recomposition w/ same keys:
reuse -> [cache value]
Recomposition w/ changed key:
invalidate -> compute -> [cache new value]
(동일 키면 재사용, 키 변경 시 무효화/재계산. — Reuse for same keys; invalidate/recompute when keys change.)
2) 왜 LocalConfiguration/LocalContext를 키로?
구성/컨텍스트 의존 계산의 정확한 갱신 (Accurate refresh of configuration/context-dependent computations)
LocalConfiguration.current: 로캘, 다크모드, 화면 크기, 폰트 스케일 등 시스템 구성이 바뀌면 값이 바뀝니다. (Changes when system configuration like locale, dark mode, screen size, or font scale changes.)LocalContext.current: 보통Activity컨텍스트이며, 구성 변경으로 액티비티가 재생성되면 새 컨텍스트가 됩니다. (Usually theActivitycontext; after configuration changes the activity is recreated, yielding a new context.)
문자열/치수 등 환경 의존적 계산을 캐시할 땐
remember(configuration, context)로 필요한 때만 재계산하세요. (For environment-dependent computations like strings/dimensions, useremember(configuration, context)so you recompute only when needed.)
@Composable
fun FlavorStep(optionIds: List<Int>) {
val configuration = LocalConfiguration.current
val context = LocalContext.current
val options = remember(configuration, context) {
optionIds.map { id -> context.getString(id) }
}
// 구성/컨텍스트가 동일한 한 options는 재사용됩니다.
// (As long as configuration/context are equal, options is reused.)
}
참고: 로캘만 의존한다면
configuration만으로 충분할 때도 있지만, 컨텍스트 교체를 안전하게 반영하려면 둘 다 키로 주는 것이 일반적으로 안전합니다. (If only locale matters,configurationmay suffice; including both keys is a safe default to reflect context swaps.)
3) 더 “추천”되는 구조 (아이템에서 변환)
부모는 리소스 ID만 전달, 자식에서 stringResource() 호출 (Pass only IDs from parent; call stringResource() in the child)
@Composable
fun SelectOptionScreen(optionIds: List<Int>) {
LazyColumn {
items(optionIds, key = { it }) { id ->
// 이 아이템만 재구성될 때 문자열을 변환 (부분 재구성)
// (Only this item recomposes when needed → finer-grained updates)
Text(text = stringResource(id))
}
}
}
- 리스트 전체를 한 번에 문자열로 변환하지 않아 불필요한 재계산/할당을 줄입니다. (Avoid bulk string conversion; fewer allocations and recalculations.)
주의:
remember { … }블록 안에서는@Composable(예:stringResource)를 호출할 수 없습니다. 필요하면context.getString(id)를 사용하세요. (You cannot call@Composablefunctions likestringResourceinsideremember { … }. Usecontext.getString(id)instead when needed.)
4) rememberSaveable / derivedStateOf와의 비교
상태 보존/파생 상태 최적화 (State persistence / derived state optimization)
-
remember- 컴포지션 생명주기 동안만 유지. 구성 변경 시 키가 없으면 그대로, 키가 있으면 재계산. (Lives for the composition; recomputes on key change.)
-
rememberSaveableBundle에 직렬화해 프로세스 종료/회전 후에도 보존. (Serializes to aBundleto survive process death/rotation.)
-
derivedStateOf { … }- 다른
State에서 파생 값을 메모이즈; 의존State가 바뀔 때만 재계산. (Memoizes a value derived from otherStates; recomputes only when dependencies change.)
val query by rememberSaveable { mutableStateOf("") } // 회전 후에도 유지 (survives rotation)
val all by remember { mutableStateOf(fullList) }
// 키 기반 캐시 (recompute only when query/all change)
val filtered1 = remember(query, all) { all.filter { it.matches(query) } }
// 스냅샷 의존 기반 파생 (recompute when either state changes)
val filtered2 by derivedStateOf { all.filter { it.matches(query) } }
5) 성능 팁 & 안티패턴
현장에서 자주 겪는 실수들 (Common pitfalls in the field)
-
무거운 작업을
remember에서 동기 실행 ✗
(Don’t run heavy blocking work insideremember.)
→ 파일 I/O·디코딩·네트워크는LaunchedEffect/produceState로 백그라운드 처리.
(UseLaunchedEffect/produceStatewith coroutines for heavy work.) -
Context를 전역 싱글톤에 보관 ✗
(Don’t keep anActivitycontext in long-lived singletons.)
→applicationContext사용 또는 필요한 데이터만 캐시.
(UseapplicationContextor cache only the extracted data.) -
키를 빼먹어 재계산 타이밍 놓침 ✗
(Forgetting keys → stale cache.)
→ 로캘/테마/배율 의존이면remember(configuration, context)를 습관화.
(Includeconfiguration/contextwhen you depend on them.)
6) FAQ 빠르게 정리 (Lightning FAQ)
- 키 비교는 어떻게 하나요?
==(구조적 동등)로 비교합니다. (Keys are compared using structural equality==.) - 값은 언제 사라지나요? 컴포저블이 컴포지션에서 제거되거나, 키가 바뀌거나, 같은 슬롯이 아니게 되면 사라집니다. (When the composable leaves the composition, keys change, or the slot changes.)
remember와rememberSaveable선택? 사용자 입력처럼 복원이 필요한 상태는rememberSaveable, 그 외엔remember. (UserememberSaveablefor restorable user-facing state; otherwiseremember.)
7) 종합 예시 (End-to-end example)
@Composable
fun FlavorOptions(optionIds: List<Int>) {
val configuration = LocalConfiguration.current
val context = LocalContext.current
// 환경 의존 변환을 키 기반으로 캐시
// (Cache environment-dependent transformation with keys)
val options = remember(configuration, context) {
optionIds.map(context::getString)
}
LazyColumn {
items(options, key = { it }) { label ->
Text(
text = label,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(16.dp)
)
}
}
}
이 예시는 구성/컨텍스트 변화에만 반응해 문자열 목록을 재계산하고, 평소 재구성에선 리스트를 재사용합니다. (Recomputes only when configuration/context change; otherwise reuses the list across recompositions.)