(TIL) Jetpack Compose에서의 상태 관리

Posted by Eun JongHyeok on March 15, 2025
  1. 상태 기반 UI
    1. 선언적 UI 패러다임
    2. 상태의 역할
  2. Compose에서 상태 관리
    1. remember
    2. rememberSaveable
    3. mutableStateOf
  3. 상태 변화와 recomposition의 연동
  4. Delegated Property와 상태 관리의 결합
  5. 결론

TIL은 가벼운 개념정리 목적으로 작성되어 있습니다.


Jetpack Compose는 선언적 UI 프레임워크로, UI를 상태 기반으로 구성합니다. 즉, 화면에 표시되는 내용은 상태(state)에 따라 달라지며, 상태가 변경되면 UI가 자동으로 다시 그려집니다.
이 글에서는 Compose에서 상태를 어떻게 관리하는지, 그리고 이를 위해 제공되는 핵심 도구들에 대해 자세히 살펴보겠습니다.


상태 기반 UI

선언적 UI 패러다임

Compose에서는 UI를 어떻게 그릴지 선언적으로 기술합니다. 예를 들어,

1
2
3
4
@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name!")
}

여기서 name이라는 상태에 따라 화면에 표시되는 텍스트가 달라집니다. 상태가 변하면, Compose는 recomposition(재구성) 과정을 통해 변경된 상태를 반영하여 UI를 업데이트합니다.

상태의 역할

  • 데이터 저장소: 상태는 UI에 표시될 데이터를 저장하는 역할을 합니다.
  • UI 업데이트 트리거: 상태가 변경되면 이를 감지해 관련된 컴포저블 함수가 다시 실행되어 최신 데이터로 UI를 그립니다.

Compose에서 상태 관리

Compose에서는 주로 remember, rememberSaveable, mutableStateOf를 활용해 상태를 관리합니다. 각 도구의 역할과 사용법을 살펴보겠습니다.

remember

remember는 Composable 함수 내에서 한 번 생성된 값을 recomposition 동안 유지하도록 합니다.

  • 목적:
    • Composable 함수가 재실행되어도 한 번 생성된 값을 보존합니다.
  • 특징:
    • 단순 캐싱 역할로, 구성 변경(예: 화면 회전)에는 상태가 보존되지 않습니다.
1
2
3
4
5
6
7
8
9
10
11
12
@Composable
fun RememberExample() {
    // 최초 컴포지션 시 mutableStateOf 객체가 생성되고, 이후 재사용됨
    var count by remember { mutableStateOf(0) }

    Column {
        Text(text = "Count: $count")
        Button(onClick = { count++ }) {
            Text("Increase")
        }
    }
}

여기서 remember에 의해 mutableStateOf(0)는 컴포저블의 메모리에 저장되고, recomposition 시 재생성되지 않습니다.


rememberSaveable

rememberSaveable은 remember와 기본 원리는 같지만, 추가적으로 구성 변경(예: 화면 회전) 시에도 상태를 저장하고 복원할 수 있습니다.

  • 목적:
    • 사용자가 화면 회전이나 다크모드 전환과 같은 구성 변경을 겪어도 상태를 유지합니다.
  • 특징:
    • Bundle 등의 저장 메커니즘을 활용해 기본 데이터 타입은 자동으로 복원합니다.
    • 복잡한 객체의 경우, Saver를 통해 복원 방식을 직접 지정할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
@Composable
fun RememberSaveableExample() {
    // 구성 변경 시에도 count 값이 유지됨
    var count by rememberSaveable { mutableStateOf(0) }

    Column {
        Text(text = "Count: $count")
        Button(onClick = { count++ }) {
            Text("Increase")
        }
    }
}

이 코드는 기기 회전 등 구성 변경 상황에서도 사용자의 상태를 그대로 유지합니다.


mutableStateOf

mutableStateOf는 Compose에서 상태를 저장하고, 상태가 변경되면 이를 감지해 관련 UI를 자동으로 업데이트하도록 만드는 핵심 도구입니다.

  • 역할:
    • 내부에 값을 저장하는 **MutableState** 객체를 생성합니다.
    • 값이 변경되면 등록된 옵저버(Composable 함수)에게 변경 사실을 알려 recomposition을 트리거합니다.
  • 내부 동작 개념:
    • 내부 백킹 필드와 값 변경 정책(예: 구조적 동등성 비교)을 사용해 불필요한 recomposition을 방지합니다.
    • operator 키워드를 통해 getValuesetValue를 구현하여, delegated property 방식(by)을 지원합니다.
1
2
3
4
5
6
7
8
9
10
11
@Composable
fun MutableStateExample() {
    var name by mutableStateOf("Compose")

    Column {
        Text(text = "Hello, $name!")
        Button(onClick = { name = "Jetpack Compose" }) {
            Text("Change Name")
        }
    }
}

버튼을 클릭하면 name이 업데이트되고, 이로 인해 해당 컴포저블 함수가 재실행되어 UI가 최신 상태로 다시 그려집니다.


상태 변화와 recomposition의 연동

Compose의 상태 관리 시스템은 내부 Snapshot 시스템과 밀접하게 연동되어 있습니다.

  • Snapshot 시스템:
    • 상태 변경 시점을 관리하고, 변경된 상태를 기반으로 컴포저블 함수들을 다시 실행합니다.
    • 이를 통해 필요한 부분만 효율적으로 업데이트하여 성능을 최적화합니다.
  • 상태 업데이트 과정:
    1. mutableStateOfsetValue가 호출되어 내부 값이 변경됩니다.
    2. 변경된 값은 Snapshot 시스템에 의해 감지되고, 해당 상태를 읽고 있는 컴포저블 함수가 재구성됩니다.
    3. 재구성된 컴포저블 함수는 최신 상태를 반영해 UI를 다시 그립니다.

이 메커니즘 덕분에, 상태 변경이 있을 때마다 직접 UI 업데이트를 신경 쓸 필요 없이 자동으로 최신 상태가 반영됩니다.


Delegated Property와 상태 관리의 결합

Kotlin의 delegated property를 활용하면 상태 관리 코드가 더욱 간결해집니다.

예를 들어, 위의 코드는 다음과 같이 작성할 수 있습니다:

  • 일반 형태:

    1
    2
    
      val countState = remember { mutableStateOf(0) }
      // 접근 시: countState.value
    
  • Delegated Property 사용:

    1
    2
    
      var count by remember { mutableStateOf(0) }
      // 접근 시: count 직접 사용
    

Kotlin 컴파일러는 내부적으로 getValuesetValue를 호출하여 countState.value에 접근하는 코드를 자동으로 생성해줍니다.
이렇게 하면 코드가 깔끔해지고, 상태를 다루는 로직이 간단해집니다.


결론

Jetpack Compose에서 상태 관리는 선언적 UI를 구현하는 데 매우 중요한 역할을 합니다.

  • remember는 단순히 recomposition 동안 상태를 유지하는 역할을 하고,
  • rememberSaveable은 구성 변경 상황에서도 상태를 복원할 수 있도록 도와줍니다.
  • mutableStateOf는 상태 변경 시 UI를 자동으로 업데이트하는 핵심 도구이며, delegated property를 활용하여 코드를 간결하게 만들 수 있습니다.

Compose의 이러한 상태 관리 메커니즘 덕분에 개발자는 UI와 상태 간의 동기화를 명시적으로 신경 쓸 필요 없이, 상태가 변경될 때마다 최신 UI를 쉽게 구현할 수 있습니다.


Android
jetpack_compose
remember
rememberSaveable
mutableStateOf
delegated_property

← Previous Post Next Post