JetPack Compose : 상태관리(remember)

timothy jeong·2022년 3월 27일
1

Android with Kotlin

목록 보기
67/69

상태(State) 란 앱의 runtime 에 변화할 수 있는 값을 의미한다. 굉장히 넓은 것을 포괄하는 정의인데, UI에 초점을 맞춰서 예시를 들어자면 '특정한 경우가 만족될때 나타나는 Snackbar', '버튼 클릭시 나타나는 애니메이션 효과', '이미지 위에 붙일 수 있는 스티커' 등이 UI 상태의 변화로 가능한 것이다.

Composable 함수는 선언적이기 때문에 UI에 변화를 주는 유일한 방법은 동일한 Composable 함수를 다른 파라미터와 함께 호출하는 것 뿐이다. 이때 주입되는 파라미터들이 UI 상태(state)를 나타내고 있을 것이다. 이러한 state 가 변화할 때마다 composable 은 recomposition (재구성) 이라는 것을 한다.

다른 관점에서 보자면, state 가 변하지 않으면 UI에 어떤 변화도 생기지 않는다는 것이다. XML 에서 EditText 에 해당하는 TextField composable 의 예시를 보자.

@Composable
fun TextFieldExample() {
   OutlinedTextField(
       label = { Text("label") }
       value = "",
       onValueChange = { },
   )
}

이러한 TextField 는 어떠한 값을 입력받아서 UI 에 변화가 생기지 않는다. TextField 는 value 값이 변화할 때 UI 가 업데이트가 되는데, value 값이 없기 때문이다. 즉, TextField 는 value 라는 파라미터에서 state 변수를 갖고 있어야 하며, onValueChange 는 이러한 value 가 변화했을 때의 행위를 람다로 지정해주는 것이다.

composable 에서 state 관리

composable 함수 내부에서는 remember composable 함수를 이용하여 단일 객체를 메모리에 저장할 수 있다. remember 에 의해 생성된 값은 초기구성(initial composition)과, 재구성(recomposition) 시에 저장된다. 이렇게 저장된 값은 remember 를 호출한 coposable 이 composition 에서 지워질때 사라진다.

remember 는 mutable, immutable 객체 모두를 저장할 수 잇다. 보통은 mutableStateOf<>(), immutableStateOf<>() 형식의 값을 저장한다.

이러한 state value 들이 변화할때 value 를 읽어서 사용하는 모든 composable 에 대해 recomposition 을 스케줄링하게 된다.

remember 를 이용하여 state value 를 선언할 때는 아래의 세가지 방식이 모두 사용될 수 있는데, 세 방식은 모두 동일한 기능을 하며, 코드의 구성상 가장 가독성이 높은 방식을 채택하면 된다.

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

remember 에 저장된 값은 UI 파라미터로 사용되는 것 외에, logic 으로도 구성할 수 있다.

@Composable
fun TextFieldExample() {
   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.h2
           )
       }
       OutlinedTextField(
           value = name,
           onValueChange = { name = it },
           label = { Text("Name") }
       )
   }
}

다른 obserable 의 사용

이러한 state 는 다른 obserable(LiveData, Flow, RxJava2) 들도 사용이 가능한다.
다만, composable 은 state<> 로 둘러쌓여진 값만 변화를 인지할 수 있기 때문에 obserable 과 composable 둘 사이를 중계해주는 추가 작업이 필요하다. LiveData<> 를 이용한다 치면 composable 에서 이 값을 읽기 전에 LiveData<>.observeAsState() 의 변화가 필요하다.

주의 사항
mutable object (ArrayList<>, mutableListOf()) 를 compose 의 state 로 이용하는 것은 예기치 못한 UI 오류를 만들어낼 수 있다. 그러므로 immutable object 를 State<> 로 감싸는 것을 추천한다.

restore state

activity가 다시 만들어질때 기존의 state 들이 초기화되지 않고, 기존의 값을 유지하게 하려면 remeberSaveable 을 이용할 수 있다.

기본적인 타입들은 remeberSaveable 로 정의된 것만으로도 Bundle 에 저장되지만, 그외의 객체들은 Parcelize 하거나 MapSaver, ListSaver 로 설정을 해줘야 Bundle 에 저장된다.

  • Parcelize
@Parcelize
data class City(val name: String, val country: String) : Parcelable

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable {
        mutableStateOf(City("Madrid", "Spain"))
    }
}
  • 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
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"))
    }
}
profile
개발자

0개의 댓글