Android/Compose

[Android] Compose의 모든 것(3) - State, MutableState, remember, rememberSaveable, by, = 선언 방식 차이

yujinius 2025. 1. 13. 17:08

 

지난 글: [Android] Compose의 모든 것(2) - Lifecycle, UI 렌더링, Recomposition과 State

https://yujinius45.tistory.com/140

 

[Android] Compose의 모든 것(2) - Lifecycle, UI 렌더링, Recomposition과 State

지난 글: [Android] Compose의 모든 것(1) - Compose, View System, Kotlinhttps://yujinius45.tistory.com/139 [Android] Compose의 모든 것(1) - Compose, View System, Kotlin시작하기에 앞서최근 Nature Album 프로젝트를 통해 Compose를

yujinius45.tistory.com


 

지난 글을 통해서 Composable의 Lifecycle과 UI 렌더링, Recomposition과 State를 전체적으로 살펴보았다.

Compose에서 상태(State)는 UI와 데이터의 동기화를 담당하는 핵심 개념으로, UI를 최신 상태로 유지하기 위한 Recomposition의 기준이 된다. 이번 포스팅에서는 Compose에서 State와 MutableState의 차이점, 상태 선언 방식(by remember, = remember, 구조 분해)의 차이를 알아보고, 상태 관리와 Recomposition 최적화의 기반을 마련해보자.


State란 무엇인가?

  • State는 Jetpack Compose에서 UI와 데이터 상태를 동기화하는 핵심 개념으로, UI를 재구성(Recomposition)하는 기준이 되는 데이터이다.
  • Compose는 상태가 변경되었을 때 UI를 자동으로 업데이트하여 최신 상태를 화면에 반영한다. 이를 Recomposition이라고 한다.
  • 그래서 상태(State) 관리를 이해하는 것은 효율적이고 최적화된 UI를 설계하는 데 매우 중요하다.

State의 특징

  1. 관찰 가능한 데이터:
    • Compose는 State를 관찰하며, 상태가 변경되면 자동으로 관련 Composable 함수가 다시 실행된다.
  2. 변경 감지 및 Recomposition:
    • 상태 변경을 감지하고, 필요한 UI 부분만 효율적으로 다시 그린다.

MutableState는 무엇인가?

MutableState는 상태를 저장하고 변경할 수 있는 객체로, 상태 변경 시 Compose가 이를 감지하여 Recomposition을 발생시킨다.


State와 MutableState의 차이점 : 변경 여부

1. State

  • State는 Jetpack Compose에서 상태를 읽기 전용으로 제공하기 위한 인터페이스이다. 상태가 변경될 수 있지만, State 인터페이스는 변경 권한을 제공하지 않으며, 읽기 전용으로 상태를 노출한다.
  • 특징:
    • 읽기 전용: State를 통해 상태를 읽을 수만 있으며, 변경할 수 없다.
    • Immutable-like: 상태의 읽기 전용 뷰를 제공하여 UI 컴포넌트에 안전하게 전달 가능하다.
    • 일반적으로 하위 Composable에 상태를 전달할 때 사용하여, 하위 Composable이 상태를 수정하지 못하도록 한다.
  • 사용 예제:
@Composable
fun Greeting(name: State<String>) {
    Text(text = name.value) // 읽기 전용
}

2. MutableState

  • MutableState는 Compose에서 상태를 저장하고 변경할 수 있는 데이터 객체이다. 상태가 변경되면 Compose는 이를 감지하고, 관련된 UI를 다시 구성(Recomposition)한다.
  • 특징:
    • 읽기/쓰기 가능: 상태의 값을 읽고, 변경할 수 있다.
    • Recomposition 트리거: 상태 값이 변경될 때 관련 Composable 함수가 Recomposition된다.
    • Compose 내부에서 상태를 저장하는 데 가장 많이 사용되는 유형이다.
  • 사용 예제:
val nameState = remember { mutableStateOf("World") }

@Composable
fun Greeting() {
    val name = nameState.value
    TextField(value = name, onValueChange = { newValue ->
        nameState.value = newValue // 상태 변경
    })
}

 


3. 주요 차이점

특징 State   MutableState
정의 상태를 읽기 전용으로 제공하는 인터페이스 상태를 읽고 쓰기 가능한 데이터 객체
읽기/쓰기 읽기 전용 읽기 및 쓰기 가능
Recomposition 직접적으로 상태를 변경하지 않음 값 변경 시 관련 Composable 함수 Recomposition
권장 사용 방식 상태를 하위 Composable에 전달할 때 사용 상태를 저장하고 관리할 때 사용
예시 State<String> MutableState<String>

 


4. 함께 사용하는 예제

State와 MutableState를 함께 사용하면, 상태를 읽기 전용으로 하위 Composable에 전달할 수 있다. (추후 State를 하위로 전달하는 것은 State Hoisting에서 더 살펴보도록 하자)

@Composable
fun ParentComposable() {
    val nameState = remember { mutableStateOf("Compose") }

    ChildComposable(name = nameState) // 읽기 전용으로 전달
}

@Composable
fun ChildComposable(name: State<String>) {
    Text(text = "Hello, ${name.value}!") // 읽기만 가능
}

 

  • MutableState는 상태를 저장하고 관리할 때 사용하며, 상태 변경이 가능한 객체이다.
  • State는 읽기 전용 상태를 제공하여, 상태를 안전하게 노출할 때 사용된다.
  • 두 객체를 적절히 활용하면 상태 관리와 캡슐화를 쉽게 구현할 수 있다.

[참고]
- Jetpack Compose에서는 UI와 상태를 동기화하기 위해 MutableState<T>뿐만 아니라 다양한 관찰 가능한(observable) 유형을 지원한다.

- Observable과 Compose: Flow, LiveData 등의 관찰 가능한 데이터와 Compose의 연동 방식.

- Observable이란 무엇인가?
    Observable은 데이터의 변화를 관찰하고, 변경 사항을 구독자(Observer)에게 통지할 수 있는 객체이다. 대표적으로 LiveData, Flow와 같은 데이터 스트림이 Observable로 작동한다.

 

컴포저블에서 MutableState 객체를 선언하는 데는 세 가지 방법

참고 링크: https://developer.android.com/develop/ui/compose/state?hl=ko

 

컴포저블에서 MutableState 객체를 선언하는 데는 세 가지 방법이 있다.

val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }

위의 선언에서 remember란?

  • remember는 Composable 함수 내에서 메모리에 상태를 저장하여, Recomposition 시 상태를 유지하도록 돕는다. 그러나 구성 변경(Configuration Change)에는 상태를 유지하지 못한다.

그렇다면 어떻게 Configuration Change에서 상태를 유지할까?

remember와 rememberSaveable의 차이

  • rememberSaveable를 사용하면 Configuration Change에서 상태를 유지할 수 있다.

rememberSaveable이란?

  • rememberSaveable은 remember와 달리 구성 변경에도 상태를 유지할 수 있도록 한다.
  • 또한, 데이터가 Bundle로 저장될 수 있어야 한다.

rememberSaveable automatically saves any value that can be saved in a Bundle
참고 링크: https://developer.android.com/develop/ui/compose/state

 


1. MutableState 객체를 선언 방식의 차이가 무엇일까?

(1) by remember

  • by 키워드는 State 객체의 값을 위임(delegate) 받아, 상태 값을 간단히 접근하고 수정할 수 있도록 한다.
  • 특징:
    • getValue와 setValue를 통해 상태 값을 읽고 쓰는 작업을 간단히 처리.
    • 코드가 간결해지지만, 상태를 읽는 순간 Recomposition 대상이 된다.
var text by remember { mutableStateOf("Hello, World!") }

@Composable
fun Greeting() {
    Text(text = text) // 즉시 상태 읽기
}

 

 

(2) = remember

  • =를 사용하면 MutableState 객체 자체를 변수에 저장하여 직접 상태를 관리할 수 있다.
  • 특징:
    • 상태 객체를 직접 다루므로, 값을 읽거나 수정하려면 .value를 명시적으로 사용해야 한다.
    • 상태 객체 자체를 전달할 때 불필요한 Recomposition을 방지할 수 있다.
val textState = remember { mutableStateOf("Hello, World!") }

@Composable
fun Greeting() {
    Text(text = textState.value) // 상태를 명시적으로 읽기
}

 

(3) 구조 분해 (Destructuring)

  • Kotlin의 구조 분해 선언을 사용하여, MutableState 객체를 상태 값과 상태 변경 함수로 분리.
  • 특징:
    • 상태 값을 명확히 분리하여 사용할 수 있어 직관적이고 가독성이 높다.
    • 상태 값을 읽을 때만 Recomposition이 발생하며, 상태 변경 함수는 독립적이다.
val textState = remember { mutableStateOf("Hello, World!") }

@Composable
fun Greeting() {
    Text(text = textState.value) // 상태를 명시적으로 읽기
}

 


2. 주요 차이점

특징 by remember = remember 구조 분해
구문 간결성 상태 값에 바로 접근 가능 .value를 사용해 명시적으로 접근 상태 값과 변경 함수로 분리하여 명확히 사용
Recomposition 대상 상태 값을 읽는 순간 Recomposition 대상 상태 객체 자체는 Recomposition 대상 아님 상태 값만 Recomposition 대상
상태 변경 방식 = 없이 바로 상태 값을 수정 가능 .value를 통해 수정 상태 변경 함수를 호출해 값 수정
가독성 간결하지만 내부 동작 파악이 어려울 수 있음 명시적 접근으로 내부 동작 파악이 쉬움 상태 값과 변경 함수가 분리되어 직관적
사용 예 간단한 UI 상태 관리 상태 객체 자체를 전달해야 하는 경우 입력 필드와 같이 상태 값을 읽고 변경해야 하는 경우
Recomposition 최적화 덜 최적화 (즉시 상태 읽기로 구독 발생) 더 최적화 (명시적 구독 관리) 더 최적화 (필요한 상태 값만 Recomposition)

 


3. Recomposition 최적화 관점

  • by remember:
    • 상태 값을 읽는 순간 해당 Composable은 상태를 구독(subscribe) 하게 되므로, 불필요한 Recomposition이 발생할 가능성이 높다.
    • 상태를 직접 전달할 때 주의가 필요하다.
  • = remember:
    • 상태 객체를 직접 다루므로, 상태를 읽기 전까지 Recomposition이 발생하지 않는다.
    • 하위 Composable에 상태 객체를 전달할 때 불필요한 Recomposition을 방지할 수 있다.
  • 구조 분해:
    • 상태 값과 상태 변경 함수를 분리하여 명확한 상태 관리가 가능하다.
    • 상태 값을 읽는 부분에서만 Recomposition이 발생하여 최적화에 유리하다.

4. 언제 어떤 방식을 사용할까?

상황 권장 방식 이유
간단한 상태 관리 by remember 상태 값만 간단히 읽고 수정하는 경우 코드가 간결함.
상태 객체를 하위에 전달 = remember 상태를 직접 읽지 않고 전달하는 경우 Recomposition을 방지.
상태 값과 변경 함수를 명확히 분리 구조 분해 상태 읽기와 변경이 분리되어 직관적이고, 최적화에 유리함.
Recomposition 최적화가 중요 = remember 또는 구조 분해 상태 객체를 다루거나 필요한 값만 읽는 방식으로 불필요한 Recomposition 방지.

5. 정리

  • by remember: 간단한 상태 관리에 적합하지만, Recomposition 최적화가 덜 이루어질 수 있음.
  • = remember: 상태 객체를 직접 전달하거나 구독을 제어하고 싶을 때 적합.
  • 구조 분해: 상태 값과 변경 로직을 명확히 분리해야 할 때 가장 적합하며, 최적화에도 유리.

6. 프로젝트에서의 확인 사항

  • by는 get value를 하기 때문에 by로 state를 가져오면 바로 읽어서 remcoposition 대상이 된다. 반면에 state를 =으로 가져오면 아직 읽은 상태가 아니라 recomposition에서 제외된다. 그래서 state hoisting 같은 것을 할 때 by로 넘겨주는 것이 아닌 state를 =으로 넘겨줘야 불필요한 recomposition이 일어나지 않았었다.
  • 관련 문서는 아래와 같다. 관련 문서에서는 by remember와 = remember로 인해 발생한 Recomposition 문제를 실험 및 검증한 사례를 다루고 있다.
  • https://github.com/boostcampwm-2024/and04-Nature-Album/wiki/by_remember의_리컴포지션
 

by_remember의_리컴포지션

🍀 주변의 다양한 생물을 촬영하고 식별하여 나만의 생물 도감을 만들고, 생물 지도를 친구와 함께 확인해보아요~ - boostcampwm-2024/and04-Nature-Album

github.com


마무리

이와 같이 State를 선언하고 관리하는 방법을 알아보았다.
Compose 프로젝트에서 Recomposition 최적화는 UI 성능에 큰 영향을 미친다. by remember는 간결하지만 Recomposition 대상이 될 가능성이 높으며, = remember와 구조 분해 방식은 필요할 때만 Recomposition을 발생시킬 수 있어 상황에 맞게 적절히 사용해야 한다. 다음 글에서는 State Hoisting, Stateless와 Stateful 설계 방법 등을 통해 상태 관리를 더 깊이 이해해보도록 하자.