[Compose Study] Jetpack Compose의 상태

728x90
반응형

* 상태(state)는 시간이 지남에 따라 변하는 값(예: 채팅 앱에서 마지막으로 받은 메시지)이다. 하지만 상태가 업데이트되는 이유는 무엇일까? Android 앱에서는 이벤트에 대한 응답으로 상태가 업데이트된다. 이벤트는 애플리케이션 외부 또는 내부에서 생성되는 입력이다.

 

  • 버튼 누르기 등으로 UI와 상호작용하는 사용자
  • 기타 요인(예: 새 값을 전송하는 센서 또는 네트워크 응답)

앱 상태로 UI에 표시할 항목에 관한 설명이 제공되고, 이벤트라는 메커니즘을 통해 상태가 변경되고 UI도 변경된다.

 

Key idea: State is. Events happen. (상태는 존재하고, 이벤트는 발생한다)

 

 * 이벤트는 어떤 일이 발생했다고 프로그램 일부에 알려준다. 든 Android 앱에는 다음과 같은 핵심 UI 업데이트 루프가 있다.

  • 이벤트: 이벤트는 사용자 또는 프로그램의 다른 부분에 의해 생성된다.
  • 상태 업데이트: 이벤트 핸들러가 UI에서 사용하는 상태를 변경한다.
  • 상태 표시: 새로운 상태를 표시하도록 UI가 업데이트 된다.

 

* Compose 앱은 구성 가능한 함수를 호출하여 데이터를 UI로 변환한다. 컴포저블을 실행할 때 Compose에서 빌드한 UI에 관한 설명을 컴포지션이라고 한다. 상태가 변경되면 Compose는 영향을 받는 구성 가능한 함수를 새 상태로 다시 실행하고, 리컴포지션이라는 업데이트된 UI가 만들어진다. 또한 Compose는 데이터가 변경된 구성요소만 재구성하고 영향을 받지 않는 구성요소는 건너뛰도록 개별 컴포저블에 필요한 데이터를 확인한다.

  • 컴포지션: 컴포저블을 실행할 때 Jetpack Compose에서 빌드한 UI에 관한 설명.
  • 초기 컴포지션: 처음 컴포저블을 실행하여 컴포지션을 만든다.
  • 리컴포지션: 데이터가 변경될 때 컴포지션을 업데이트하기 위해 컴포저블을 다시 실행하는 것.

* 이렇게 하려면 Compose가 추적할 상태를 알아야 한다. 그래야 업데이트를 받을 때 리컴포지션을 예약할 수 있다.

Compose에는 특정 상태를 읽는 컴포저블의 리컴포지션을 예약하는 특별한 상태 추적 시스템이 있다. 이를 통해 Compose가 세분화되어 전체 UI가 아닌 변경해야 하는 이러한 구성 가능한 함수만 재구성할 수 있다. 이 작업은 '쓰기'(즉, 상태 변경)뿐만 아니라 상태에 대한 '읽기'도 추적하여 실행된다.

* Compose의 State  MutableState 유형을 사용하여 Compose에서 상태를 관찰할 수 있도록 합니다.

* Compose는 상태 value 속성을 읽는 각 컴포저블을 추적하고 그 value가 변경되면 리컴포지션을 trigger한다. mutableStateOf 함수를 사용하여 관찰 가능한 MutableState를 만들 수 있다. 이 함수는 초기값을 State 객체에 래핑된 매개변수로 수신한 다음, value의 값을 관찰 가능한 상태로 만든다.

 

* 아래 예시에서는 count가 초기값이 0 mutableStateOf API를 사용하도록 WaterCounter 컴포저블을 업데이트한다. mutableStateOf MutableState 유형을 반환하므로 value를 업데이트하여 상태를 업데이트할 수 있고 Compose는 value를 읽는 이러한 함수에 리컴포지션을 트리거한다.

@Composable
fun WaterCounter(modifier: Modifier = Modifier) {
   Column(modifier = modifier.padding(16.dp)) {
       // Changes to count are now tracked by Compose
       val count: MutableState<Int> = mutableStateOf(0)

       Text("You've had ${count.value} glasses.")
        Button(onClick = { count.value++ }, Modifier.padding(top = 8.dp)) {
           Text("Add one")
       }
   }
}

 

* 하지만 이렇게 해도 버튼을 눌렀을때 count가 화면에서 증가하지는 않는다. 리컴포지션은 잘 작동하지만 리컴포지션이 발생하면 count 변수가 다시 0으로 초기화되기 때문이다.

* 리컴포지션 간에 이 값을 유지할 방법이 필요한데, 이를 위해 remember를 사용하면 된다. remember로 계산된 값은 초기 컴포지션 중에 컴포지션에 저장되고 저장된 값은 리컴포지션 간에 유지된다.

val count: MutableState<Int> = remember { mutableStateOf(0) }

 

* 또는 Kotlin의 delegate properties을 사용하여 count 사용을 간소화할 수 있다.

by 키워드를 사용하여 count를 var로 정의할 수 있다. delegate의 getter 및 setter 가져오기를 추가하면 매번 MutableState의 value 속성을 명시적으로 참조하지 않고도 count를 간접적으로 읽고 변경할 수 있다. (count.value 대신 그냥 count만 사용 가능)

var count by remember { mutableStateOf(0) }

 

 

* remember로 상태저장을 했지만, 기기를 가로모드로 변경하거나, 언어를 변경하거나 다크모드로 변경하는 등의 작업을 하면 저장되어있던 상태가 날아가버리게 된다.

* remember를 사용하면 리컴포지션 간에 상태를 유지하는 데 도움이 되지만 구성 변경 간에는 유지되지 않는다. 이를 위해서는 remember 대신 rememberSaveable을 사용해야 한다. rememberSaveable은 Bundle에 저장할 수 있는 모든 값을 자동으로 저장한다. 다른 값의 경우에는 맞춤 Saver 객체를 전달할 수 있다.

var count by rememberSaveable { mutableIntStateOf(0) }

 

* Activity가 다시 생성된 후 UI 상태를 복원하려면 rememberSaveable을 사용한다. 리컴포지션 간에 상태를 유지하는 것 외에도 rememberSaveable은 Activity 재생성 및 시스템에서 시작된 프로세스 종료 전반에 걸쳐 상태를 유지한다.

* 앱의 상태 및 UX 요구사항에 따라 remember를 사용할지 rememberSaveable을 사용할지 고려해야 한다.

728x90
반응형
TAGS.

Comments