안드로이드 시스템·Compose 용어 사전
ComposerememberSaveable

remember

컴포지션에 값을 기억시켜 리컴포지션마다 다시 만들지 않게 하는 것.

컴포지션에 값을 기억시켜 리컴포지션마다 다시 만들지 않게 한다.

var text by remember { mutableStateOf("") }         // 리컴포지션을 넘는다
var text by rememberSaveable { mutableStateOf("") } // 구성 변경·프로세스 죽음도 넘는다

세 가지 죽음

리컴포지션구성 변경프로세스 죽음
일반 변수
remember
rememberSaveable
ViewModel

key로 무효화한다

@Composable
fun UserDetail(userId: Long) {
    // userId 가 바뀌면 새로 계산한다. 안 주면 옛 사용자 데이터가 남는다
    val formatted = remember(userId) { heavyFormat(userId) }
}

remember { } 는 key 가 없다 = 컴포지션에 남아 있는 한 절대 다시 계산하지 않는다

  • 파라미터에 의존하는 값이면 반드시 key 를 준다
  • 이것이 "목록을 스크롤했더니 옛 항목 데이터가 보인다" 의 흔한 원인

무거운 계산을 감싼다

// ❌ 리컴포지션마다 정렬한다 (스크롤 중 초당 수십 번)
val sorted = items.sortedBy { it.name }

// ✅ items 가 바뀔 때만
val sorted = remember(items) { items.sortedBy { it.name } }

rememberSaveable의 제약

// Bundle 에 담을 수 있는 타입만 가능하다
@Parcelize data class Filter(val q: String, val tag: String?) : Parcelable
var filter by rememberSaveable { mutableStateOf(Filter("", null)) }

// 그 외에는 Saver 를 직접 준다
val saver = listSaver<Range, Int>(
    save = { listOf(it.start, it.end) },
    restore = { Range(it[0], it[1]) }
)
var range by rememberSaveable(stateSaver = saver) { mutableStateOf(Range(0, 10)) }

Bundle 크기 한도(약 1MB) 를 공유한다

  • 목록 데이터를 rememberSaveable 에 담으면 TransactionTooLargeException
  • 큰 것은 ViewModel + DB, 작은 것만 여기

면접 함정

  • "remember를 쓰면 상태가 유지된다" → 컴포지션을 떠나면(조건부로 사라지면) 값도 사라진다.
  • "rememberSaveable이면 ViewModel이 필요 없다" → 비즈니스 상태와 UI 요소 상태는 자리가 다르다.

컴포지션을 떠나면 사라진다

if (expanded) {
    var draft by remember { mutableStateOf("") }    // 접었다 펴면 초기화된다
    TextField(draft, { draft = it })
}

remember 는 '컴포지션에 남아 있는 동안' 만 값을 보관한다 조건부로 사라지는 컴포저블 안의 상태는 그때마다 초기화된다

유지해야 한다면 상태를 위로 올린다 (호이스팅)

remember vs derivedStateOf vs LaunchedEffect

  • remember(key) { 계산 } — key 가 바뀔 때 다시 계산한다. 동기 계산

  • derivedStateOf { 계산 } — 입력 상태가 바뀔 때 재평가하되, 결과가 바뀔 때만 알린다

  • LaunchedEffect(key) { } — suspend 작업. key 가 바뀌면 취소 후 재시작

  • 셋을 헷갈려 remember 안에서 네트워크를 부르는 코드가 흔하다

    • remember 는 컴포지션 중에 즉시 실행된다 = 메인 스레드 블로킹

mutableStateOf의 세 가지 정책

mutableStateOf(0)                                        // 구조적 동등성 (기본)
mutableStateOf(0, referentialEqualityPolicy())          // 참조 동등성
mutableStateOf(list, neverEqualPolicy())                // 항상 변경으로 본다

// 원시 타입 전용 — 박싱을 피한다
mutableIntStateOf(0) · mutableFloatStateOf(0f) · mutableLongStateOf(0L)

기본 정책은 equals 로 비교해 같으면 아예 알리지 않는다

  • data class 를 copy 로 같은 값으로 만들면 리컴포지션이 안 돈다 (의도된 최적화)

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — Compose 상태·side-effect — remember·LaunchedEffect