Android/Compose

[Android Compose] Compose에서 상태 저장하기(remember)

참깨빵위에참깨빵_ 2023. 4. 23. 17:50
728x90
반응형

Compose를 사용하기 전의 XML로 뷰를 짤 경우 상태는 다양한 방법으로 정의한다. boolean 변수에 값을 저장한다거나 객체를 활용한다거나, 또는 다른 소스들을 찾아보면 아주 낮은 확률로 보이는 AtomicBoolean을 쓴다던가 등. 데이터 바인딩을 적극 사용한다면 LiveData 또는 Flow를 사용할 수도 있다.

그러나 Compose에선 boolean, 객체를 활용하는 방식으로 뷰의 상태를 관리하지 않는다. Compose에선 remember라는 API를 사용해 여러 리컴포지션 간에 상태를 관리할 수 있다. 안드로이드 디벨로퍼에선 remember에 대해 아래와 같이 말한다.

 

https://developer.android.com/jetpack/compose/state?hl=ko 

 

상태 및 Jetpack Compose  |  Android Developers

상태 및 Jetpack Compose 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 앱의 상태는 시간이 지남에 따라 변할 수 있는 값을 의미합니다. 이는 매우 광범위한 정

developer.android.com

앱의 상태는 시간이 지남에 따라 변할 수 있는 값을 의미한다. 이는 매우 광범위한 정의로 Room DB부터 클래스 변수까지 모든 항목이 포함된다...(중략)...Compose는 선언적이므로 Compose를 업데이트하는 유일한 방법은 새 인수로 동일한 컴포저블을 호출하는 것이다. 이런 인수는 UI 상태를 표현한다. 상태가 업데이트될 때마다 재구성이 실행된다. 따라서 TextField 같은 항목은 명령형 XML 기반 뷰에서처럼 자동 업데이트되지 않는다. 컴포저블이 새 상태에 따라 업데이트되려면 새 상태를 명시적으로 알려야 한다
Composable 함수는 remember API를 써서 메모리에 객체를 저장할 수 있다. remember에 의해 계산된 값은 초기 컴포지션 중에 컴포지션에 저장되고 저장된 값은 리컴포지션 중에 반환된다. remember는 변경 가능한 객체와 변경할 수 없는 객체를 저장할 때 모두 사용할 수 있다. remember는 객체를 컴포지션에 저장하고 remember를 호출한 컴포저블이 컴포지션에서 삭제되면 그 객체를 잊는다...(중략)

 

https://www.valueof.io/blog/jetpack-compose-remember-vs-mutablestateof

 

Compose remember vs remember mutableStateOf — Mobile Dev Notes

Manage state with remember mutableStateOf to cause recomposition of dependent composables when state changes

www.valueof.io

remember는 컴포지션 동안 값을 한 번만 계산하고 리컴포지션 동안 반환한다. 모든 내부 컴포저블은 해당 값을 가져오고 해당 컴포저블의 일부가 재구성돼도 변경되지 않는다...(중략)...remember는 컴포지션에 객체를 저장하고 remember를 사용하는 컴포지션이 소멸될 때 이런 객체를 제거한다는 걸 기억하라. 기억된 값이 configuration change를 유지하려면 계산 결과를 Bundle에 저장하는 rememberSaveable을 사용하라

 

https://heegs.tistory.com/122

 

[Jetpack] Compose 사용하기 - 1. remember와 MutableState

Jetpack Compose를 공부하며 예제를 만들어보는 도중, 처음 접하게 되는 키워드들과 클래스, 함수들이 상당히 많았다. 해당 키워드와 클래스들을 사용하지 않으면 Compose를 사용하는 것에 무리가 있

heegs.tistory.com

기본적으로 컴포즈에서 어떤 상태값이 바뀌면 리컴포지션(재구성)이 발생한다. 재구성이란 말 그대로 재생성한다는 뜻이다. a값을 기본으로 가진 버튼을 누르면 b값으로 바뀌는 텍스트뷰가 있다고 가정한다. 버튼을 누르면 상태값이 바뀌게 되는데(a -> b) 이 때 재구성이 일어나 UI를 다시 그리게 된다. 다시 그리게 되면 b라는 값을 갖는 게 아닌 기본값인 a가 나오게 되어 사용자가 원하는 동작이 이뤄지지 않는다. 따라서 재구성됐을 때도 값을 저장할 수 있게 하기 위해 컴포즈에선 remember 키워드를 제공한다...(중략)

 

remember가 하는 역할을 정리하면 아래와 같다.

 

  • remember는 메모리에 객체를 저장하는 API다. 계산되어 만들어진 값은 컴포지션 도중에 저장되어 리컴포지션 도중에 반환된다
  • remember로 변경 가능한 객체, 변경 불가능한 객체 모두를 저장할 수 있다
  • remember를 호출하면 UI 전체를 다시 그리는 게 아니라 변경이 필요한 부분만 변경한다

 

그리고 remember를 사용하기 위해선 mutableStateOf()라는 함수를 자주 사용해 바뀌는 값을 관리한다. 안드로이드 코드랩의 코드 중 일부를 예시로 가져왔다.

 

@Composable
private fun Greeting(name: String) {
    val expanded = remember { mutableStateOf(false) }
    val extraPadding = if (expanded.value) 48.dp else 0.dp
    Surface(
        color = MaterialTheme.colors.primary,
        modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
    ) {
        Row(modifier = Modifier.padding(24.dp)) {
            Column(modifier = Modifier.weight(1f).padding(bottom = extraPadding)) {
                Text("Hello, ")
                Text(name)
            }
            ElevatedButton(onClick = { expanded.value = !expanded.value }) {
                Text(if (expanded.value) "Show less" else "Show more")
            }
        }
    }
}

 

expanded라는 변수에 remember {}의 결과값을 담고 있다. 그리고 블록 안을 보면 mutableStateOf(false)를 호출하고 있다. 대충 expanded의 초기값을 false로 설정하는 것 같이 보인다. mutableStateOf()는 뭘 하는 함수인가? 위와 똑같은 안드로이드 디벨로퍼에선 아래와 같이 말한다.

 

mutableStateOf()는 관찰 가능한 MutableState<T>를 생성하는데 이는 런타임 시 컴포즈에 통합되는 관찰 가능한 유형이다

 

다른 설명도 같이 본다.

 

https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#mutableStateOf(kotlin.Any,androidx.compose.runtime.SnapshotMutationPolicy) 

 

androidx.compose.runtime  |  Android Developers

androidx.car.app.managers

developer.android.com

전달된 값으로 초기화된 새 MutableState를 반환한다. MutableState 클래스는 컴포즈에서 단일 읽기, 쓰기를 관찰하는 단일 값 홀더다. 이에 대한 쓰기는 스냅샷 시스템의 일부로 처리된다

 

즉 mutableStateOf()를 호출하면 false라는 boolean 값을 갖는 MutableState 객체가 만들어지고, 이를 감싸고 있는 remember에 의해 컴포지션 도중에 값이 저장되고 리컴포지션 도중에 값을 반환한다.

제일 밑의 ExpandedButton의 onClick 속성과 Text() 안에서 expanded의 값이 변화하거나 이를 읽어서 UI를 변경하는 걸 볼 수 있다.

 

더 짧은 간단한 예제도 디벨로퍼에서 소개하고 있다.

 

import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember

val count = remember { mutableStateOf(0) }

Text(text = "You clicked ${count.value} times")
Button(onClick = { count.value++ }) {
    Text("Click me")
}

 

위 예제는 count 변수를 0으로 초기화한 뒤 버튼을 누를 때마다 count 변수값을 1씩 더하고 Text()를 통해 더한 결과값을 문자열 사이에 섞어서 같이 표시한다.

 

그러나 remember만 써서는 화면 회전 같은 configuration change가 발생했을 때 대처할 수 없다. configuration change를 처리하려면 위에서 말한 대로 rememberSaveable을 써야 한다. 그게 아니라면 remember를 사용하면 어지간한 상태 관리는 적절하게 처리할 수 있다.

반응형