Jetpack Compose의 State

State

시간이 지남에 따라 변할 수 있는 값

  • 네트워크 연결되지 않을 때 표시되는 스낵바
  • 게시글 및 댓글
  • 사용자가 클릭하면 재생되는 애니메이션

Compose와 상태

Compose를 업데이트하는 유일한 방법은 새로운 인수로 동일한 Composable 함수를 호출하는 것이다(Recomposition). 이 과정은 자동으로 업데이트 되는 것이 아니기 때문에 상태를 업데이트하려면 새 상태를 명시적으로 지정해야 한다. 이는 Compose에서 composition과 recomposition의 작동 방식과 관련이 있다.

Composition : Jetpack Compose가 Composable을 실행할 때 빌드한 UI에 대한 명세 Initial composition : 처음 Composable을 실행하여 생성된 composition Recomposition : 데이터가 변경될 때 composition을 업데이트하기 위해 Composable을 다시 실행하는 것

@Composable
fun HelloContent() {
   Column(modifier = Modifier.padding(16.dp)) {
       Text(
           text = "Hello!",
           modifier = Modifier.padding(bottom = 8.dp),
           style = MaterialTheme.typography.h5
       )
       OutlinedTextField(
           value = "",
           onValueChange = { },
           label = { Text("Name") }
       )
   }
}

TextField는 자동으로 업데이트되지 않는다. TextField의 업데이트는 value 변경에 의해 일어난다.

State

Composable 함수는 remember API를 이용하여 메모리에 객체를 저장한다.

MutableState<T>

interface MutableState<T> : State<T> {
    override var value: T
}

value가 변경되면 value를 사용하는 Composable의 recomposition이 예약된다.

Composable에서 MutableState를 선언하는 데에는 3가지 방법이 있다.

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

Delegation property를 이용하기 위해선 아래 두 구문을 import해야 한다.

import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

MutableState<T>value는 다른 Composable의 매개변수로 사용하거나 문의 로직으로 사용할 수 있다.

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

rememberSaveable

rememberSaveable에는 Bundle에 저장할 수 있는 모든 값을 자동으로 저장한다. Bundle에 저장할 수 없을 경우 맞춤 Saver 객체를 전달할 수 있다.

rememberList

Compose에서 변경 가능한 객체(mutableListOf(), ArrayList<T> 등)를 상태로 사용할 경우 오래된 데이터가 표시될 수 있다. Mutable 객체의 내부 변화는 Compose에서 observe할 수 없으며 Recomposition이 일어나지 않는다.

기타 상태 유형

Compose는 Flow, RxJava(Kotlin), LiveData 등에서 상태를 observe하는 기능을 라이브러리 형태로 지원한다. https://developer.android.com/jetpack/compose/state?hl=ko#use-other-types-of-state-in-jetpack-compose

Stateful, Stateless

remember를 사용하여 객체를 저장하는 Composable은 Stateful한 Composable이다. Stateful한 Composble은 상태를 제어할 필요가 없고 상태를 직접 관리하지 않아도 상태를 사용할 수 있을 때 유용하다. 그러나 Stateful한 Composable은 재사용성이 적고 테스트가 어려운 경향이 있다. State를 갖지 않는 Composable은 Stateless한 Composable이다. Stateless를 달성하는 쉬운 방법은 State Hoisting을 이용하는 것이다.

State Hoisting

Hoisting : 코드가 실행되기 전 변수/함수 선언이 해당 스코프의 최상단으로 끌어올려진 것 같은 현상을 의미한다.

Compose의 State hoisting은 Composable을 stateless로 만들기 위해 state를 Composable의 호출자로 옮기는 패턴이다. 일반적인 방법은 상태 변수를 아래 두 매개변수로 바꾸는 것이다(이름이나 형식 등은 상황에 맞게 사용).

  • value: T : 표시할 값
  • onValueChange: (T) -> Unit : 값 변경 이벤트

Hoisted state는 몇가지 중요한 속성이 있는데,

  • 단일 정보 소스(Single source of truth): 상테를 복제하지 않고 옮기기 때문에 정보 소스가 하나이다.
  • 캡슐화(Encapsulated) : Stateful Composable만 상태 수정 가능
  • 공유 가능(Shareable) : Hoisted state는 여러 Composable과 공유 가능
  • 가로채기 가능(Interceptable) : Stateless composable의 호출자는 상태를 변경하기 전에 이벤트를 무시할지 수정할지 결정 가능
  • 분리됨(Decoupled) : Stateless Composable의 상태는 어디에나 저장할 수 있다(ViewModel 등에 저장 가능).
@Composable
fun HelloScreen() {
    var name by rememberSaveable { mutableStateOf("") }

    HelloContent(name = name, onNameChange = { name = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello, $name",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.h5
        )
        OutlinedTextField(
            value = name,
            onValueChange = onNameChange,
            label = { Text("Name") }
        )
    }
}

img 상태가 내려가고 이벤트가 올라가는 패턴을 단방향 데이터 흐름이라고 한다.


© 2021. All rights reserved.

Powered by Hydejack v9.1.6