목차
- Compose 시작
- Compose의 생명주기
- Compose에서의 상태(State)
- 렌더링 수행 과정
- 부수효과(Side-Effect)
- 시맨틱(Semantics)
- 아키텍쳐 레이어
- 로컬 범위지정(Scope) 데이터
Compose에서의 상태(State)
Composable함수에서의 상태
- 동일한 Composable 함수를 다른 파라미터로 호출해야만 UI가 업데이트(리컴포지션)됨.
- 즉, 함수에 전달되는 파라미터는 UI가 무엇을 렌더링해야하는지를 전달하므로
UI 상태
를 의미
- 즉, 함수에 전달되는 파라미터는 UI가 무엇을 렌더링해야하는지를 전달하므로
- 일반적으로, 파라미터를 State 타입으로 감싸서 remember() Composable 함수를 이용해 값을 저장하는 방식으로 사용함
@Composable
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
// name값을 상태로 저장
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") }
)
}
}
State
Composable 함수의 수행시에 값을 읽기위해 Compose에서 제공하는 타입.
Compose는 내부적으로,
State
타입이 가진 값의 변화가 발생하면 RecomposeScope를 이용하여 리컴포지션을 요청State
는 변경불가능한(immutable) 값을 저장하는 타입으로, 변경가능한(mutable) 값을 저장하려면 하위타입인 MutableState 를 이용MutableState
는 이전값과 다른값이 저장되는 경우에만 리컴포지션을 요청하고, 이전값과 동일한 값이 저장되는 경우에는 리컴포지션을 스킵
Composable 함수 내에서 MutableState를 선언하는 3가지 방법
// by 위임키워드를 사용하려면 import 필요
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
val mutableState = remember { mutableStateOf(defaultValue) }
var value by remember { mutableStateOf(defaultValue) }
val (value, setValue) = remember { mutableStateOf(defaultValue) }
remember
- 함수 파라미터는 함수가 수행되는 동안에만 값을 유지하고 사용가능.
- Compose에서는 메모리에 단일 객체를 저장하는 remember Composable 함수를 제공
- 가변(mutable), 불변(immutable) 모두 저장가능
- 최초 컴포지션 수행시에 값을 저장하고, 리컴포지션이 수행될 때마다 저장된 값을 반환.
- 단, 구성변경(Configuration Change)시에는 상태를 저장하지 않음.
- rememberSaveable 을 사용해야 내부적으로 Bundle 객체에 저장하는 로직 수행
State Hoisting
Stateful vs Stateless
- Stateful : Composable함수 내부에서
remember
로 상태를 저장하고 관리- 함수사용의 입장에서는 UI상태를 알아서 관리하므로 외부에서 관리할 필요가 없으나, 재사용성이 떨어지고 테스트가 어려움
- Stateless : Composable함수 내부에서 어떤 상태도 관리하지 않음.
- 외부에서 상태관리를 할 필요가 있지만, 재사용하기에 용이하고 테스트가 쉬움
State Hoisting
- State의 관리를 외부(함수 호출한 곳)에서 하도록 구성하여, Stateless한 Composable을 만드는 디자인패턴
- Composable이 state관리를 하면 안되므로, 내부에서 발생한 액션(이벤트)처리도 밖으로 전달해야 함
// 위의 HelloContent() 함수를 State Hoisting 패턴으로 변경한 예시
// HelloScreen() 함수에서 상태를 관리하는(Stateful) 역할을 담당.
@Composable
fun HelloScreen() {
// name값을 상태로 저장
var name by remember { mutableStateOf("") }
HelloContent(
name = name,
onNameChange = { name = it }
)
}
// Stateless Composable 함수. 외부에서 값(name)을 받아서 렌더링하고, 액션/이벤트(onNameChange)를 밖으로 전달
@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") }
)
}
}
이 패턴을 왜 사용하는가?
- 앱의 데이터/상태를 관리하는 부분과 화면에 렌더링하는 UI로직을 분리할 수 있다.
- 데이터와 UI가 각각의 변경에 독립적
- stateless한 Composable함수로 변경하여 재사용성, 테스트가능성을 높일 수 있다.
Compose에서의 상태 복원
- Activity나 프로세스가 재생성될 때, 상태를 유지하기 위해 rememberSaveable Composable 함수를 사용
- rememberSaveable은 내부적으로
Bundle
을 이용하므로,Bundle
에 저장할 수 없는 객체라면 변경해주어야 함
Parcelize
- 저장할 클래스에 @Parcelize어노테이션을 명시하여 Parcelable을 지원
// City 클래스를 Parcelable 지원하도록 변경
@Parcelize
data class City(val name: String, val country: String) : Parcelable
@Composable
fun CityScreen() {
var selectedCity = rememberSaveable {
mutableStateOf(City("Madrid", "Spain"))
}
}
Saver
Parcelable을 지원할 수 없는 객체인 경우, Saver를 이용하여 Bundle에 저장/복원 로직을 직접 지정하도록 Compose에서 지원함
mapSaver : 객체의 저장/복원로직으로 Map을 사용한 Saver를 반환하는 함수
- save : 객체의 값을 Map에 저장한 후에 Map을 반환하도록 로직 구성
- restore : 데이터 복원시에 save에서 반환한 Map을 받아서 다시 객체로 구성
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() {
// stateSaver 파라미터를 이용하여 상태 저장/복원에 사용할 Saver를 지정
var selectedCity = rememberSaveable(stateSaver = CitySaver) {
mutableStateOf(City("Madrid", "Spain"))
}
}
- ListSaver : 객체의 저장/복원로직으로 List를 사용한 Saver를 반환하는 함수
- save : 객체의 값을 List에 저장한 후에 List를 반환하도록 로직 구성
- restore : 데이터 복원시에 save에서 반환한 List를 받아서 다시 객체로 구성
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"))
}
}
댓글 없음:
댓글 쓰기