앱에서 상태(State)란 시간에 따라 변경될 수 있는 모든 값을 의미합니다. 이는 매우 광범위한 정의로, Room 데이터베이스부터 클래스 내 변수까지 모든 것을 포함합니다.
모든 Android 앱은 사용자에게 상태를 표시합니다. Android 앱에서 상태의 몇 가지 예는 다음과 같습니다.
- 네트워크 연결을 설정할 수 없을 때 표시되는 스낵바(Snackbar)
- 블로그 게시글 및 해당 게시글에 달린 댓글
- 사용자가 버튼을 클릭할 때 재생되는 리플(Ripple) 애니메이션
- 사용자가 이미지 위에 그릴 수 있는 스티커
Jetpack Compose는 Android 앱에서 상태를 어디에, 어떻게 저장하고 사용할지를 명확하게 정의할 수 있도록 도와줍니다. 이번 포스팅은 상태와 컴포저블(Composable) 간의 연결 및 Jetpack Compose에서 상태를 더 쉽게 다룰 수 있도록 제공하는 API를 집중적으로 다룹니다.
상태(State)와 컴포지션(Composition)
Compose는 선언형(Declarative) 방식이므로 UI를 업데이트하는 유일한 방법은 새로운 인자로 동일한 컴포저블을 다시 호출하는 것입니다. 이러한 인자들은 UI 상태를 나타냅니다. 상태가 변경될 때마다 리컴포지션(Recomposition)이 발생합니다.
그 결과, TextField와 같은 요소는 기존의 명령형(XML 기반) 뷰처럼 자동으로 업데이트되지 않습니다. 컴포저블은 명시적으로 새로운 상태를 전달받아야만 업데이트됩니다.
@Composable
private fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello!",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.bodyMedium
)
OutlinedTextField(
value = "",
onValueChange = { },
label = { Text("Name") }
)
}
}
이 코드를 실행하고 TextField에 입력을 시도하면 아무 변화도 일어나지 않습니다.
그 이유는 TextField가 자체적으로 상태를 업데이트하지 않기 때문입니다.
TextField는 value 매개변수가 변경될 때만 업데이트되며, 이는 컴포지션(Composition) 및 리컴포지션(Recomposition)의 작동 방식 때문입니다.
[핵심 용어]
컴포지션(Composition): Jetpack Compose가 컴포저블을 실행하여 UI를 구성한 결과물
초기 컴포지션(Initial Composition): 컴포저블이 처음 실행될 때 UI를 생성하는 과정
리컴포지션(Recomposition): 데이터가 변경될 때 기존 UI를 업데이트하기 위해 컴포저블을 다시 실행하는 과정
초기 컴포지션과 리컴포지션에 대해 더 알고 싶다면 https://jtm0609.tistory.com/308를 참고하세요.
컴포저블에서의 상태(State)
컴포저블 함수는 remember API를 사용하여 객체를 메모리에 저장할 수 있습니다.
remember를 통해 계산된 값은 초기 컴포지션(Initial Composition) 동안 컴포지션(Composition)에 저장되며, 이후 리컴포지션(Recomposition) 시 저장된 값이 반환됩니다.
remember는 변경 가능한 객체(mutable objects)와 변경 불가능한 객체(immutable objects) 모두를 저장하는 데 사용할 수 있습니다.
참고:
remember는 객체를 컴포지션 내에 저장하지만, 이를 호출한 컴포저블이 컴포지션에서 제거되면 해당 객체도 잊혀집니다.
mutableStateOf는 MutableState<T> 객체를 생성하는데, 이는 런타임 시 Compose에 통합되는 Observable한 유형입니다.
interface MutableState<T> : State<T> {
override var value: T
}
value가 변경되면, 이를 읽고 있는 모든 컴포저블 함수가 리컴포지션(Recomposition) 되도록 예약됩니다.
컴포저블에서 MutableState 객체를 선언하는 방법은 세 가지가 있습니다.
// 방법 1: 일반적인 MutableState 선언
val mutableState = remember { mutableStateOf(default) }
// 방법 2: 'by' 위임(delegate) 사용
var value by remember { mutableStateOf(default) }
// 방법 3: 구조 분해(Destructuring) 사용
val (value, setValue) = remember { mutableStateOf(default) }
위 세 가지 선언 방식은 동일한 기능을 수행하며, 단지 코드 가독성을 높이기 위한 구문 차이 입니다.
작성하는 코드에서 가장 읽기 쉬운 방법을 선택하면 됩니다.
단 by 위임 구문에는 다음 import 문이 필요합니다.
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
상태를 이용한 UI 변경 예제
remember를 사용하여 상태를 저장하고, 이 상태에 따라 컴포저블을 동적으로 변경할 수 있습니다.
예를 들어, 입력된 이름이 비어있으면 "Hello" 메시지를 표시하지 않도록 할 수 있습니다.
@Composable
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
var name by remember { mutableStateOf("") }
if (name.isNotEmpty()) {
Text(
text = "Hello, $name!",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.bodyMedium
)
}
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") }
)
}
}
위 코드에서 remember를 사용하면 리컴포지션(Recomposition) 이 발생해도 상태를 유지할 수 있습니다.
하지만, 화면 회전과 같은 구성 변경(Configuration Change) 이 발생하면 상태가 유지되지 않습니다.
만약 구성 변경에서도 상태를 유지하려면 rememberSaveable을 사용해야 합니다.
rememberSaveable은 Bundle에 저장할 수 있는 값을 자동으로 저장합니다.
주의:
ArrayList<T> 또는 mutableListOf() 같은 변경 가능한(Mutable) 객체를 Compose에서 상태로 사용하면,사용자가 오래된 데이터(Stale Data)를 보게 될 가능성이 큽니다.
ArrayList나 변경 가능한 데이터 클래스(Mutable Data Class) 는 Compose에서 관찰 불가능(Non-Observable) 합니다. 따라서, 내부 값이 변경되더라도 Compose는 이를 감지하지 못하며 리컴포지션이 발생하지 않습니다. 내부 요소만 변경되고 객제 자체는 변하지 않기 때문입니다.
이러한 이유로 관찰 가능한(Observable) 데이터 홀더인 State<List<T>>을 사용하고 listOf()와 같은 불변 리스트(Immutable List)를 함께 활용 하는 것이 좋습니다.
다른 지원되는 상태 유형(State Types)
Compose에서는 상태를 저장할 때 반드시 MutableState<T>를 사용할 필요는 없습니다.
Android 앱에서 자주 사용하는 다른 관찰 가능한(Observable) 데이터 타입도 지원됩니다.
하지만 Compose에서 관찰 가능한 타입을 사용하려면 반드시 State<T>로 변환해야 합니다.
그래야만 상태 변화가 발생할 때 자동으로 리컴포지션(Recomposition) 이 이루어집니다.
Compose는 Android 앱에서 자주 사용하는 관찰 가능한 타입을 State<T>로 변환할 수 있도록 다양한 함수를 제공합니다.
아래에 해당 변환을 위해 필요한 아티팩트 및 방법을 정리하였습니다.
1. Flow: collectAsStateWithLifecycle()
collectAsStateWithLifecycle()은 Flow에서 값을 수집하면서도 생명주기를 인식(Lifecycle-aware)하여 리소스를 절약할 수 있도록 도와줍니다.
Compose의 State로 변환된 후, Flow에서 방출되는 최신 값을 나타냅니다.
val state by myFlow.collectAsStateWithLifecycle()
Android 앱에서 Flow를 사용할 때 권장되는 방식입니다.
2. Flow: collectAsState()
collectAsState()는 Flow에서 값을 수집하여 Compose의 State로 변환하는 기능을 합니다.
그러나 collectAsStateWithLifecycle()처럼 Lifecycle을 고려하지 않으므로 Android 전용이 아닌, 플랫폼 독립적인 코드에 적합합니다.
val state by myFlow.collectAsState(initial = "Default Value")
collectAsStateWithLifecycle()는 Android 전용이므로, 멀티플랫폼 프로젝트에서는 collectAsState()를 사용하는 것이 좋습니다.
3. LiveData: observeAsState()
observeAsState()는 LiveData를 Compose의 State로 변환하여 UI에서 직접 사용할 수 있도록 합니다.
LiveData의 값을 변경하면 Compose에서 자동으로 리컴포지션이 발생합니다.
val state by myLiveData.observeAsState(initial = "Default Value")
4. RxJava2: subscribeAsState()
subscribeAsState()는 RxJava2의 Single, Observable, Completable을 Compose의 State로 변환합니다.
val state by myObservable.subscribeAsState(initial = "Default Value")
5. RxJava3: subscribeAsState()
RxJava3의 Single, Observable, Completable을 Compose State로 변환하는 기능을 합니다.
val state by myObservable.subscribeAsState(initial = "Default Value")
핵심포인트:
- Compose는 State 객체를 읽을 때 자동으로 리컴포지션(Recomposition)을 수행합니다.
- 만약 LiveData 같은 다른 관찰 가능한 타입(Observable Type)을 Compose에서 사용하려면, 반드시 State<T>로 변환 후 읽어야 합니다.
- 이를 위해, LiveData<T>.observeAsState() 같은 컴포저블 확장 함수(Composable Extension Function) 를 사용해야 합니다.
상태가 있는(Stateful) vs 상태가 없는(Stateless) 컴포저블
✅ 상태가 있는(Stateful) 컴포저블
- remember를 사용하여 내부적으로 상태를 저장하는 컴포저블은 Stateful(상태가 있는) 컴포저블이 됩니다.
- 예를 들어, HelloContent 컴포저블은 내부적으로 name 상태를 저장하고 수정하므로 상태가 있는 컴포저블입니다.
@Composable
fun HelloContent() {
var name by remember { mutableStateOf("") } // 내부 상태 저장
Column(modifier = Modifier.padding(16.dp)) {
if (name.isNotEmpty()) {
Text(text = "Hello, $name!")
}
OutlinedTextField(
value = name,
onValueChange = { name = it }
)
}
}
✔️ Stateful 컴포저블이 유용한 경우
- 상태를 직접 관리하지 않고, 간단하게 사용할 때
- 호출자가 상태 관리에 신경 쓸 필요가 없을 때
❌ Stateful 컴포저블의 단점
- 재사용성이 낮아지고 테스트가 어려워짐
- 외부에서 상태를 제어할 수 없음
✅ 상태가 없는(Stateless) 컴포저블
- 내부적으로 상태를 저장하지 않는 컴포저블을 Stateless(상태가 없는) 컴포저블이라고 합니다.
- Stateless 컴포저블을 만들려면 상태를 외부에서 전달받도록 해야 합니다.
- 이를 State Hoisting(상태 끌어올리기) 이라고 합니다.
@Composable
fun HelloContentStateless(name: String, onNameChange: (String) -> Unit) {
Column(modifier = Modifier.padding(16.dp)) {
if (name.isNotEmpty()) {
Text(text = "Hello, $name!")
}
OutlinedTextField(
value = name,
onValueChange = onNameChange
)
}
}
✔️ Stateless 컴포저블의 장점
- 재사용성이 높고, 테스트가 쉬움
- 외부에서 상태를 관리할 수 있어 더 유연함
재사용성을 높이기 위해, Stateful과 Stateless 버전 둘 다 제공하는 것이 일반적입니다.
Stateful 버전은 간단한 사용을 원할 때 제공하고, Stateless 버전은 호출자가 상태를 직접 제어할 수 있도록 합니다.
상태 호이스팅
State Hoisting(상태 끌어올리기) 는 컴포저블 내부의 상태를 호출하는 부모 컴포저블로 이동시켜, 해당 컴포저블을 Stateless(상태가 없는) 컴포저블로 만드는 패턴입니다.
상태 호이스팅의 일반적인 패턴
컴포저블 내부에서 직접 상태를 관리하는 대신, 두 개의 매개변수를 추가하여 상태를 외부에서 관리할 수 있도록 합니다.
- value: T → 현재 표시할 값
- onValueChange: (T) -> Unit → 값 변경을 요청하는 이벤트 (새로운 값을 전달받음)
📌 하지만 onValueChange에 국한되지 않습니다.
더 구체적인 이벤트가 필요하면 적절한 람다 함수를 정의할 수도 있습니다.
상태 호이스팅의 주요 특징
1. 단일 진실 공급원(Single source of truth)
- 상태를 중복 저장하지 않고, 한 곳에서만 관리하여 버그를 방지할 수 있습니다.
2. 캡슐화(Encapsulation)
- 상태 변경은 Stateful 컴포저블에서만 가능하며, Stateless 컴포저블은 이를 직접 수정할 수 없습니다.
3. 재사용성(Reusable)
- 끌어올린 상태는 여러 컴포저블에서 재사용 가능합니다.
- 예를 들어, name 값을 다른 컴포저블에서도 읽을 수 있습니다.
4. 인터셉트 가능(Interceptable)
- Stateless 컴포저블을 호출하는 쪽에서 이벤트를 가로채거나 수정할 수 있습니다.
5. 관심사 분리(Decoupled)
- Stateless 컴포저블이 상태 저장 방식과 완전히 분리되므로,
ViewModel 또는 다른 계층에서 상태를 관리할 수 있게 됩니다.
6. 테스트 용이성(Testability)
- Stateless 컴포저블은 상태를 관리하지 않으므로, UI 테스트가 더 쉬워집니다.
아래 코드는 HelloContent 내부의 상태(name)를 HelloScreen으로 이동시키는 과정입니다.
📌 1. 상태를 관리하는 Stateful 컴포저블 (HelloScreen)
@Composable
fun HelloScreen() {
var name by rememberSaveable { mutableStateOf("") } // 상태를 끌어올림
HelloContent(name = name, onNameChange = { name = it }) // 상태 전달 및 변경 이벤트 처리
}
rememberSaveable을 사용하여 구성 변경(Configuration Change) 시에도 상태를 유지하도록 합니다.
📌 2. 상태를 받는 Stateless 컴포저블 (HelloContent)
@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello, $name",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.bodyMedium
)
OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") })
}
}
HelloContent는 더 이상 내부적으로 상태를 관리하지 않으며, name을 외부에서 받아 사용합니다.
즉, 상태 저장 방식과 완전히 분리된 재사용 가능한 Stateless 컴포저블이 되었습니다.
상태가 내려가고 이벤트가 올라가는 패턴을 단방향 데이터 흐름이라고 합니다.
- HelloScreen에서 HelloContent로 상태(name)가 내려갑니다.
- HelloContent에서 입력이 변경되면 이벤트가 HelloScreen으로 올라갑니다.
이러한 단방향 데이터 흐름 방식은 UI를 상태 관리와 분리하여 유지보수를 쉽게 하고, 앱의 예측 가능성을 높여줍니다.
핵심포인트: 상태를 끌어올릴 때(State Hoisting) 어디에 상태를 위치시킬지 결정하는 3가지 규칙이 있습니다.
1. 상태는 해당 상태를 읽는 모든 컴포저블의 최소 공통 부모로 끌어올려야 합니다. (읽기)
2. 상태는 변경될 가능성이 있는 가장 높은 수준으로 끌어올려야 합니다. (쓰기)
3. 동일한 이벤트에 의해 변경되는 두 개 이상의 상태는 함께 끌어올려야 합니다.
이러한 규칙에서 요구하는 것보다 상태를 더 높은 곳으로 끌어올리는 것은 가능하지만, 필요 이상으로 끌어올릴 필요는 없습니다.
반대로 상태를 충분히 끌어올리지 않으면, 단방향 데이터 흐름(UDF)을 따르는 것이 어려워지거나 불가능해질 수 있습니다.
Compose에서 상태 복원하기
rememberSaveable
- rememberSaveable은 remember와 유사하게 동작하며, 리컴포지션이 발생해도 상태를 유지할 수 있습니다.
- 추가적으로, remember와 달리 화면 회전(구성 변경)이나 프로세스 재생성 시에도 상태를 유지할 수 있습니다.
- 이는 Saved Instance State 메커니즘을 활용하여 구현됩니다.
⚠️ 주의
- rememberSaveable은 사용자가 앱을 완전히 종료하면 상태를 유지하지 않습니다.
- 예를 들어, 사용자가 최근 앱 목록에서 스와이프하여 종료하면 상태가 초기화됩니다.
상태를 저장하는 방법 (Ways to Store State)
rememberSaveable은 Bundle에 저장할 수 있는 기본 데이터 타입(String, Int, Boolean, List, Serializable 등)을 자동으로 저장합니다.
하지만 Bundle에 저장할 수 없는 객체를 저장하려면 추가적인 작업이 필요합니다.
이 경우, 다음 세 가지 방법을 사용할 수 있습니다.
1. @Parcelize 사용 (Parcelize)
- 가장 간단한 방법은 객체에 @Parcelize 어노테이션을 추가하여 Parcelable 객체로 만드는 것입니다.
- Parcelable 객체는 Bundle에 저장할 수 있으므로 자동으로 상태가 유지됩니다.
@Parcelize
data class City(val name: String, val country: String) : Parcelable
@Composable
fun CityScreen() {
var selectedCity = rememberSaveable {
mutableStateOf(City("Madrid", "Spain"))
}
}
@Parcelize를 사용하면 City 객체가 자동으로 Bundle에 저장될 수 있는 Parcelable 객체로 변환됩니다.
따라서 rememberSaveable을 사용할 때 별도의 추가 작업 없이 상태를 유지할 수 있습니다.
2. mapSaver 사용
- @Parcelize를 사용할 수 없는 경우, mapSaver를 사용하여 객체를 Map 형태로 변환하여 저장할 수 있습니다.
- 이 방식은 객체를 key-value 형태로 변환하여 Bundle에 저장합니다.
data class City(val name: String, val country: String)
val CitySaver = run {
val nameKey = "Name"
val countryKey = "Country"
mapSaver(
save = { mapOf(nameKey to it.name, countryKey to it.country) },
restore = { City(it[nameKey] as String, it[countryKey] as String) }
)
}
@Composable
fun CityScreen() {
var selectedCity = rememberSaveable(stateSaver = CitySaver) {
mutableStateOf(City("Madrid", "Spain"))
}
}
mapSaver를 사용하면 원하는 방식으로 객체를 변환하여 저장하고 복원할 수 있습니다.
키 값을 직접 지정해야 하는 점이 단점이지만, @Parcelize를 사용할 수 없는 경우 유용합니다.
3. listSaver 사용
- listSaver는 Map을 사용하는 대신, List를 사용하여 저장하는 방식입니다.
- Map의 key-value 대신 List의 index를 사용하여 값을 저장하고 복원합니다.
- 키를 따로 정의하지 않아도 되므로, mapSaver보다 더 간단한 방식입니다.
data class City(val name: String, val country: String)
val CitySaver = listSaver<City, Any>(
save = { listOf(it.name, it.country) }, // List로 저장
restore = { City(it[0] as String, it[1] as String) } // Index로 복원
)
@Composable
fun CityScreen() {
var selectedCity = rememberSaveable(stateSaver = CitySaver) {
mutableStateOf(City("Madrid", "Spain"))
}
}
listSaver는 mapSaver보다 간결하며, 객체를 리스트로 변환하여 저장할 때 유용합니다.
키를 정의할 필요 없이 리스트 인덱스를 사용하여 값을 저장하고 복원할 수 있습니다.
Compose의 상태 홀더
간단한 State Hoisting(상태 끌어올리기) 는 컴포저블 함수 내에서 직접 관리할 수 있습니다.
하지만 관리해야 할 상태가 많아지거나, 로직이 복잡해질 경우
상태와 로직을 다른 클래스로 위임하는 것이 좋은 방법입니다.
이러한 역할을 하는 클래스를 State Holder(상태 보관자) 라고 합니다.
핵심 용어:
State Holders: 컴포저블의 로직과 상태를 관리하는 클래스
remember를 다시 실행하는 방법 (Retriggering remember calculations)
remember는 일반적으로 MutableState와 함께 사용됩니다.
var name by remember { mutableStateOf("") }
위 코드에서는 remember를 사용하여 name 값을 리컴포지션(Recomposition) 동안 유지합니다.
📌 remember 동작 방식:
1️⃣ remember는 계산 람다(Calculation Lambda) 블록을 실행하고, 결과 값을 저장합니다.
2️⃣ 리컴포지션이 발생해도 저장된 값을 반환하여 불필요한 재계산을 방지합니다.
3️⃣ 컴포지션에서 제거되면 저장된 값도 삭제됩니다.
remember는 리컴포지션이 발생할 때마다 다시 계산하는 것을 방지하는 역할을 합니다.
특히 비용이 많이 드는 객체를 캐싱하는 데 유용합니다.
예를 들어, ShaderBrush 객체 생성은 비용이 많이 드는 연산이므로, remember를 사용하여 캐싱할 수 있습니다.
val brush = remember {
ShaderBrush(
BitmapShader(
ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(),
Shader.TileMode.REPEAT,
Shader.TileMode.REPEAT
)
)
}
remember는 기본적으로 컴포지션에서 제거될 때까지 값을 유지합니다.
하지만, 특정 값이 변경되었을 때 다시 계산하도록 강제하는 방법도 있습니다.
이를 위해, remember는 key(키) 값을 받을 수 있습니다. 키 값이 변경되면 remember는 캐시를 무효화하고, 다시 람다 블록을 통해 계산을 수행합니다.
아래 코드에서는 Key로 설정한 배경 이미지(avatarRes)가 변경될 때마다 새로운 ShaderBrush 객체를 생성하는 예시입니다.
@Composable
private fun BackgroundBanner(
@DrawableRes avatarRes: Int,
modifier: Modifier = Modifier,
res: Resources = LocalContext.current.resources
) {
val brush = remember(key1 = avatarRes) {
ShaderBrush(
BitmapShader(
ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(),
Shader.TileMode.REPEAT,
Shader.TileMode.REPEAT
)
)
}
Box(
modifier = modifier.background(brush)
) {
/* ... */
}
}
다음 코드에서는 상태를 일반 상태 보관자 클래스(MyAppState)로 호이스팅하 예제를 보여줍니다.
이를 통해 Compose의 리컴포지션을 고려하면서도 상태를 효과적으로 관리할 수 있습니다.
@Composable
private fun rememberMyAppState(
windowSizeClass: WindowSizeClass
): MyAppState {
return remember(windowSizeClass) {
MyAppState(windowSizeClass)
}
}
@Stable
class MyAppState(
private val windowSizeClass: WindowSizeClass
) { /* ... */ }
rememberMyAppState는 컴포저블 내부에서 상태 보관자(MyAppState)를 생성하는 함수입니다. 이 함수는 remember(windowSizeClass)를 사용하여 리컴포지션이 발생해도 동일한 상태를 유지합니다.
하지만 windowSizeClass가 변경되면 새로운 MyAppState 객체가 생성됩니다. 예를 들어, 사용자가 화면을 회전하여 기기의 크기가 변경되면 새로운 MyAppState가 생성됩니다.
remember의 키(Key) 변경 감지 방식
- Compose는 remember에 전달된 key 값(windowSizeClass)의 변경을 감지하여 기존 값을 무효화합니다.
- 기본적으로 클래스의 equals 구현을 사용하여 값이 변경되었는지 확인합니다.
- 값이 변경되면 remember는 새로운 객체를 생성하고 기존 캐시를 삭제합니다.
remember와 derivedStateOf의 차이
- remember를 키와 함께 사용하는 방식은 derivedStateOf와 유사해 보일 수 있습니다.
- 하지만 두 개념은 사용 목적이 다릅니다.
API | 역할 | 주요 차이점 |
remember(key) | 특정 키 값이 변경될 때만 새로운 객체를 생성하여 상태 유지 | 키 값이 변경되기 전까지 동일한 객체 유지 |
derivedStateOf {} | 기존 상태를 기반으로 새로운 상태를 계산 | 기존 상태가 변경될 때마다 새로운 값 계산 |
RememberSavable로 상태 저장하기
rememberSaveable은 remember의 래퍼(Wrapper)이며,
데이터를 Bundle에 저장할 수 있어 활동(Activity) 재생성 및 시스템 프로세스 종료 후에도 상태를 유지할 수 있습니다.
rememberSaveable은 리컴포지션(Recomposition) 뿐만 아니라, Activity가 다시 생성되거나, 시스템이 프로세스를 종료한 후에도 상태를 유지할 수 있도록 합니다.
rememberSaveable은 remember처럼 키(Key)를 받으며, 입력값이 변경되면 캐시가 무효화(Invalidate)되고, 다음 리컴포지션 시 다시 계산됩니다.
단, rememberSavable에서는 같은 역할을 하는 키 값을 inputs라고 합니다.
다음 예에서 rememberSaveable는 typedQuery가 변경될 때까지 userTypedQuery를 저장합니다.
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) {
mutableStateOf(
TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length))
)
}
remember vs rememberSaveable의 차이점
API | 상태 유지 범위 | 캐시 무효화 트리거 | 저장 방식 |
remember | 리컴포지션 동안 유지 | 키 변경, 컴포지션 제거 시 | 메모리 내 저장 |
rememberSaveable | 리컴포지션 + Activity 재생성 + 시스템 프로세스 종료 후에도 유지 | 입력값(inputs) 변경, 앱 종료 시 | Bundle을 사용하여 저장 |
끝.
참고자료
상태 및 Jetpack Compose | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. 상태 및 Jetpack Compose 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 앱의 상태는 시간이 지남에 따라
developer.android.com
'Android > Compose' 카테고리의 다른 글
[Android] Compose의 부수효과(SideEffect) (0) | 2025.03.09 |
---|---|
[Android] Compose 상태 호이스팅 위치 정하기 (0) | 2025.03.07 |
[Android] Compose의 UI 렌더링 동작 매커니즘 (0) | 2025.03.07 |
[Android] Composable의 Lifecycle (0) | 2025.03.06 |
[Android] Compose에 대한 이해 (1) | 2025.03.06 |