본문 바로가기
Android.Kotlin

[Android] JetPack Compose 에서 상태 관리 (remember, rememberSaveable)

by 동동하다 2023. 9. 5.
반응형

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"))
    }
}
반응형