JetPack Compose 는 선언적으로 Compse 를 생성합니다. 그래서 선언적으로 생성된 Compose를 업데이트 할 수 있는 유일한 방법은 새로운 인수로 동일한 Composable 을 호출하는 것입니다. 여기서 새로운 인수가 바로 상태 (State) 입니다. 즉 JetPack Compose 에서 상태는 UI 를 어떻게 표현할지에 대한 표현 값이라고 할 수 있습니다.
Composable 의 생명주기와 remember
우선 Compsable 의 생명 주기에 대해 간단하게 알 필요가 있습니다. Android 다른 Activity 나 Fragment 와 같이 Composable 또한 생명주기를 가지지만 다른 컴포넌트보다 훨씬 심플합니다.
- initial Composition : 처음 호출된 Composable 상태
- Recomposition : 상태가 변경됨에 따라 UI 가 다시 그려지는 상태
- Decomposition : Composable 이 파괴 될 때
여기서 Recomposition 이 일어날 때 변경된 상태를 저장하기 위해 사용하는 것이 remember 키워드 입니다.
그럼 remember 에 저장되는 상태는 어떤 Type 일까요?
그건 MutableState<T> 입니다.
interface MutableState<T> : State<T> {
override var value: T
}
이런 MutableState 를 생성하는 가장 쉬운 방법은 mutableStateOf 함수를 사용하는 것입니다.
@Composable
fun rememberExample() {
var state = remember { mutableStateOf("") }
TextField(
value = state.value,
onValueChange = {text:String -> state.value = text},
modifier = Modifier.wrapContentSize()
)
}
위 예시에서 TextField 의 onValueChage 메소드가 호출 되면서 state 값이 변경됩니다. 그리고 변경된 state 값이 TextField 의 value 로 출력됩니다.
여기서 remember 를 사용하지 않으면 어떻게 될까요?
@Composable
fun rememberExample() {
var state = mutableStateOf<String>("")
TextField(
value = state.value,
onValueChange = {text:String -> state.value = text},
modifier = Modifier.wrapContentSize()
)
}
위 코드에서 onValueChange 에서 state 값이 변경 되어 Recomposition 이 발생하지만, state 값을 "" 값으로 초기화 하기 때문에 사실상 TextField 의 값이 변경되지 않습니다.
상태를 선언하는 3가지 방식
Composable 에서 MutableState 를 선언하는 방식은 3가지가 있습니다.
val mutableState = remember { mutableStateOf()}
var value by remeber { mutableStateOf() }
val (value, setValue) = remember { mutableStateOf() }
이러한 선언은 동일한 것이며, 원하는 것을 선택해서 사용하시면 됩니다.
다만, by 구문을 사용하기 위해서는 별도의 import 가 필요합니다.
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
rememberSaveable
remember 를 통해서 Recomposition 될 때 상태를 유지 할 수 있지만 구성 전반에 변화가 생겼을 때는 상태가 유지 되지 않습니다. 가장 대표적인 예가 가로, 세로 전환입니다. 이 경우 Activity 가 새로 그려지기 때문에 새로운 상태가 생성 됩니다.
이러한 경우 rememberSaveable 을 사용할 수 있습니다. rememberSaveable 은 Bundle 에 상태 값을 저장하기에 앱을 강제로 종료하지 않은 이상 구성 전반에 변화가 생기지 않은 이상 상태 값이 저장됩니다.
위의 rememberExample 코드에서 rememberSaveable 을 사용하면 가로모드로 변경하더라도 값이 유지 됨을 볼 수 있습니다.
@Composable
fun rememberSaveableExample() {
var state = rememberSaveable { mutableStateOf("") }
TextField(
value = state.value,
onValueChange = {text:String -> state.value = text},
modifier = Modifier.wrapContentSize()
)
}
rememberSaveable 의 경우 bundle 에서 지원하는 type 만을 지원합니다. 그래서 custom 하게 생성된 object 를 저장하고 싶다면 별도의 3가지 방법을 사용해야 합니다.
Parcelize
가장 간단한 방법으로 Parcelize 를 이용하는 방법입니다.
우선 built.gradle 에 plugin 을 추가 합니다.
plugins {
id 'kotlin-parcelize'
}
그리고 상태를 유지하고 싶은 object 에 @Parcelize 를 붙여주면 됩니다.
@Parcelize
data class City(val name: String, val country: String) : Parcelable
@Composable
fun CityScreen() {
var selectedCity = rememberSaveable {
mutableStateOf(City("Madrid", "Spain"))
}
}
MapSaver
key/value 형태인 Map 을 상태로 유지하고 싶다면 MapSaver 를 사용할 수 있습니다.
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"))
}
}
ListSaver
간단하게 리스트 형태로 하고 싶다면 ListSaver 를 사용하면 됩니다.
data class City(val name: String, val country: String)
val CitySaver = listSaver<City, Any>(
save = { listOf(it.name, it.country) },
restore = { City(it[0] as String, it[1] as String) }
)
@Composable
fun CityScreen() {
var selectedCity = rememberSaveable(stateSaver = CitySaver) {
mutableStateOf(City("Madrid", "Spain"))
}
}
'Android.Kotlin' 카테고리의 다른 글
[Kotlin] 범위 지정 함수(scope function) 란? apply, run, let, with, also (0) | 2023.09.19 |
---|---|
[Android] Android Studio 이전 버전 다운로드 (0) | 2023.09.13 |
[Android] Coroutine Builder, Suspend 함수 이해하기 (0) | 2023.09.11 |
[Android] Kotlin CoroutineScope 에 대해 알아보자 (0) | 2023.09.08 |
[Android] JetPack Compose 에서 permission 요청하기 (0) | 2023.09.05 |