[Android]Compose 상태 관리: mutableStateOf, derivedStateOf, produceState, rememberUpdatedState

gay0ung·2025년 9월 3일
1

Android

목록 보기
13/14

Jetpack Compose에서 상태 관리를 어떻게 해야 할지 헷갈리는 경우가 많음. mutableStateOf, derivedStateOf, produceState, rememberUpdatedState 같은 API들이 각각 언제 쓰는 건지, 그리고 =by 차이가 뭔지 정리해봄.


mutableStateOf

가장 기본적인 상태 생성 방법임. 값을 저장하고, 그 값을 읽는 Composable에서 자동으로 재구성이 일어남.

var count by remember { mutableStateOf(0) }

Button(onClick = { count++ }) {
    Text("$count")
}
  • 내부적으로 State<T>를 만들어서 Compose Snapshot 시스템이 변경을 추적함.
  • 프리미티브 타입은 mutableIntStateOf, mutableFloatStateOf 같은 최적화 버전을 쓰면 GC 덜 발생함.

derivedStateOf

다른 상태에서 계산된 값을 만들고 싶을 때 사용함. 비싼 계산을 캐싱해서 불필요한 recomposition을 줄임.

val items by remember { mutableStateOf(listOf(1, 2, 3)) }
val total by remember { derivedStateOf { items.sum() } }

Text("합계: $total")
  • 입력 상태가 바뀔 때만 계산됨.
  • 정렬, 필터링, 포맷 변환 같은 데에 유용함.

produceState

비동기 소스나 콜백 기반 데이터를 Compose의 상태로 바꾸고 싶을 때 씀. 내부적으로 코루틴을 돌려서 State<T>를 업데이트함.

@Composable
fun Weather(city: String) {
    val weather by produceState<String?>(initialValue = null, key1 = city) {
        value = repository.fetchWeather(city) // suspend 가능
    }
    Text(weather ?: "Loading…")
}
  • key가 바뀌면 코루틴이 재시작됨.
  • LaunchedEffect + mutableStateOf 조합을 한 줄로 합쳐놓은 느낌임.

사실 나는 원래 네트워크 호출 같은 비동기 데이터를 처리할 때
var data by remember { mutableStateOf<T?>(null) }
LaunchedEffect(key) { data = repository.getSomething(key) }
이런 식으로 섞어 썼음.
근데 produceState를 보니까 이 패턴을 아예 API 차원에서 지원해주는 거라 훨씬 깔끔해 보였음.
개인적으로 이 부분이 꽤 흥미롭게 다가왔음.


rememberUpdatedState

Effect 블록 안에서 최신 값을 안전하게 참조하고 싶을 때 쓰는 API임. LaunchedEffectDisposableEffect 같은 블록은 오래 실행되는데, 그 안에서 사용하는 값이 recomposition 때 바뀌면 이전 값을 계속 잡고 있을 수 있음. 이걸 막아줌.

@Composable
fun Timer(onTimeout: () -> Unit) {
    val currentOnTimeout by rememberUpdatedState(onTimeout)

    LaunchedEffect(Unit) {
        delay(3000)
        currentOnTimeout() // 항상 최신 onTimeout 실행됨
    }
}
  • mutableStateOf와 달리 recomposition을 트리거하지 않음.
  • 단지 최신 값을 캡처하는 안전 장치임.

= vs by

Compose에서 상태를 선언할 때 두 가지 방식이 있음.

val countState = remember { mutableStateOf(0) }   // = 방식
var count by remember { mutableStateOf(0) }       // by 방식
  • countStateMutableState<Int> 객체임. 값을 읽거나 쓸 때 .value를 붙여야 함.

    Text("${countState.value}")
    countState.value++
  • countby 델리게이트 문법 덕분에 그냥 값처럼 쓸 수 있음.

    Text("$count")
    count++
  • 동작 차이는 없음. 둘 다 Compose Snapshot 시스템에 의해 감지되고 recomposition 발생함.

  • 차이는 문법 설탕(syntax sugar) 차이일 뿐임.

  • State 객체 자체를 넘기거나 다뤄야 하면 =, 값만 쓰고 싶으면 by가 편함.


정리

  • UI에 직접 보여줄 값 → mutableStateOf
  • 비싼 계산 파생값 → derivedStateOf
  • 외부 비동기 데이터 소스 → produceState
  • Effect 블록에서 최신 값만 안전하게 쓰고 싶을 때 → rememberUpdatedState
  • = vs by는 단순히 문법 차이, 동작은 동일함
  • 원래는 mutableStateOf + LaunchedEffect 조합을 자주 썼는데, produceState가 그 패턴을 깔끔하게 대체할 수 있다는 점이 인상적이었음

이렇게 정리해두면 Compose 상태 관리 관련해서 선택지가 명확해짐.
나처럼 원래 mutableStateOfLaunchedEffect를 붙여 쓰던 사람이라면, produceState가 꽤 반가울 수 있음!!!

0개의 댓글