![article thumbnail image](https://blog.kakaocdn.net/dn/bDGAWv/btrHd0Zr0iD/k53VDgpvCM1r0s9XgZig41/img.png)
상태에 대한 예시
- 네트워크 연결을 설정할 수 없을 때 표시되는 스낵바
- 블로그 게시물 및 관련 댓글
- 사용자가 클릭하면 버튼에서 재생되는 물견 애니메이션
- 사용자가 이미지 위에 그릴 수 있는 스티커
상태 및 컴포지션
Compose는 선언적이므로 Compose를 업데이트하는 유일한 방법은 새 인수로 동일한 컴포저블을 호출하는 것이다. 이러한 인수는 UI 상태를 표현하고 상태가 업데이트 될 때마다 재구성이 실행된다. 따라서 TextField와 같은 항목은 명령현 XML 기반 뷰에서처럼 자동으로 업데이트 되지 않는다. 컴포저블이 새 상태에 따라 업데이트 되려면 새 상태를 명시적으로 알려야 한다.
@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가 자체적으로 업데이트 되지 않기 때문이다. value 매개변수가 변경될 때 업데이트 된다. 이는 Compose에서의 컴포지션 및 리컴포지션 작동 방식 때문이다.
컴포저블의 상태
구성 가능한 함수는 remember API를 사용하여 메모리에 객체를 저장할 수 있다. remember에 의해 계산된 값은 초기 컴포지션 중에 컴포지션에 저장되고 저장된 값은 리컴포지션 중에 반환된다. remember는 변경 가능한 객체뿐만 아니라 변경할 수 없는 객체를 저장하는데 사용할 수 있다. mutableStateOf는 관찰 가능한 MutableState<T>를 생성하고 이는 런타임 시 Compose에 통합되는 관찰 가능 유형이다.
interface MutableState<T> : State<T> {
override var value: T
}
value가 변경되면 value를 읽는 컴포져블 함수의 리컴포지션이 예약된다. ExpandingCard의 경우 expanded가 변경될 때마다 ExpandingCard가 재구성된다.
컴포저블에서 MutableState 객체를 선언하는 데는 세가지 방법이 있다.
- val mutableState = remember { mutableStateOf(default) }
- var value by remember { mutableStateOf(default) }
- val (value, setValue) = remember { mutableStateOf(default) }
- remember는 객체를 컴포지션에 저장하고,
- remember를 호출한 컴포저블이 컴포지션에서 삭제되면 그 객체를 삭제한다
- remember는 변경 가능한 객체뿐만 아니라 변경할 수 없는 객체를 저장하는 데에도 사용할 수 있다.
- remember는 리컴포지션(재구성)에서 상태를 보존하는 데 도움이 된다.
스테이트풀(Stateful)과 스테이트리스(Stateless)
remember를 사용하여 객체를 저장하는 컴포저블은 내부 상태를 생성하여 컴포저블을 스테이트풀(Stateful)로 반든다. HelloContent는 내부적으로 name 상태를 보존하고 수정하므로 스테이트풀 컴포저블의 한 예가 된다. 이는 호출자가 상태를 제어할 필요가 없고 상태를 직접 관리하지 않아도 상태를 사용할 수 있는 경우에 유용하다. 그러나 내부 상태를 갖는 컴포저블은 재사용 가능성이 적고 테스트하기 더 어려운 경향이 있다.
스테이트리스(Stateless) 컴포저블은 상태를 갖지 않는 컴포저블이다. 스테이트리스를 달성하는 한 가지 쉬운 방법은 상태 호이스팅을 사용하는 것이다.
재사용 가능한 컴포저블을 개발할 때에는 동일한 컴포저블의 스테이트풀 버전과 스테이트리스 버전을 모두 노출해야 하는 경우가 있다. 스테이트풀 버전은 상태를 염두에 두지 않는 호출자에게 펴리하며 스테이트리스 버전은 상태를 제어하거나 끌어올려야 하는 호출자에게 필요하다.
상태 호이스팅
Compose에서 상태 끌어올리기(Hoisting)는 컴포저블을 스테이트리스로 만들기 위해 상태를 컴포저블의 호출자로 옮기는 패턴이다. 상태 호이스팅을 통해 Composable의 State를 해당 Composeable을 호출하는 Composable 쪽으로 끌어올림으로써 자식 Composable을 Stateless하게 만드는 것이다. Jetpack Compose에서 상태를 끌어올리기 위한 일반적 패턴은 상태 변수를 다음 두 개의 매개변수로 바꾸는 것이다.
- value: T: 표시할 현재 값
- onValueChange: (T) -> Unit: T가 제안된 새 값인 경우 값을 변경하도록 요청하는 이벤트
@Composable
fun Screen() {
var name by rememberSaveable { mutableStateOf("") }
Content(name = name, onNameChange = { name = it })
}
@Composable
fun Content(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") }
)
}
}
예를 들어 Content를 Screen에서 호출한다고 하면 Content의 상태를 없애기 위해 Screen에 name: String와 onNameChange: (String) → Unit를 추가한다. 이렇게 하면 Content는 Stateless해지고 Screen으로부터 상태를 전달받는다.
추가적으로 ViewModel에만 State를 저장하도록 패턴을 수정하면 Screen Composable 또한 Stateless하도록 변경할 수 있다.
@Composable
fun Screen() {
Content(name = "HAHA", text = viewModel.text, onNameChange = { name = it })
}
상태를 끌어올려 컴포저블을 Stateless 상태로 변경하면 다양한 상황에서 재사용하고 테스트하기 더 쉽다. State가 내려가고 이벤트가 올라가는 패턴을 단방향 데이터 흐름이라고 한다. 이 경우 상태는 ViewModel → Screen → Content로 가고 이벤트는 Content → Screen → ViewModel로 올라간다. 단방향 데이터 흐름을 따르면 UI에 상태를 표시하는 컴포저블과 상태를 저장하고 변경하는 앱 부분과 분리할 수 있다.
State를 Hoisting할 때 State가 어디로 가야 하는 지 파악하기 위한 세 가지 규칙
- 읽기 - 상태를 사용하는 모든 컴포저블의 최소 공통 상위 항목으로 상태를 끌어올려야 한다.
- 쓰기 - 상태는 변경될 수 있는 최소한 가장 높은 수준으로 끌어올려야 한다.
- 동일한 이벤트에 대한 응답으로 두 상태가 변경되면 함께 끌어올려야 한다.
정보 소스로서의 ViewModel
일반 상태 홀더 클래스가 UI 로직과 UI 요소의 상태를 담당하는 경우 ViewModel은 특별한 유형의 상태 홀더로서 다음 작업을 진행한다.
- 비즈니스 레이어와 데이터 영역 같이 대개 계층 구조의 다른 레이어에 배치되는 애플리케이션의 비즈니스 로직에 대한 액세스 권한 제공
- 특정 화면에 표시하기 위한 애플리케이션 데이터 준비(화면 상태 또는 UI 상태)
ViewModel은 컴포지션보다 수명이 더 길다. 구성 변경 후에도 그대로 유지되기 때문이다. ViewModel은 수명이 길기 때문에 컴포지션의 수명에 바인인된 상태에 관한 장기 지속 참조를 보유해서는 안된다. 관련 장기 지속 참조를 보유하면 메모리 누수가 발생할 수 있다.
화면 수준 컴포저블에서 ViewModel 인스턴스를 사용하여 비즈니스 로직 액세스 권한을 제공하고 UI 상태 정보 소스가 되는 것이 좋다. ViewModel 인스턴스를 다른 컴포저블에 전달해서는 안된다.
'안드로이드 > UI' 카테고리의 다른 글
Compose에서 "donut-hole skipping" 란 무엇인가 (0) | 2022.08.17 |
---|---|
Compose 생명주기 (0) | 2022.07.31 |
Compose란 무엇인가 (0) | 2022.07.14 |