안드로이드 개발에서 제트팩 컴포즈(Jetpack Compose)는 UI를 구축하는 현대적인 방식을 제공합니다. 특히 상태 관리는 컴포즈 애플리케이션에서 중요한 부분인데, 이를 효과적으로 관리하기 위해 ViewModel
과 함께 collectAsState()
를 사용하는 방법을 알아보겠습니다.
collectAsState()
는 Jetpack Compose에서 Flow
(또는 StateFlow
)를 쉽게 구독하고, 구독 결과를 Compose의 State
로 변환해주는 확장 함수입니다. 이 함수를 사용하면 Flow
가 새 값을 emit
할 때마다 UI가 자동으로 재컴포지션되어 최신 상태를 표시해줍니다.
State
) 변화를 관찰해 UI를 업데이트합니다.collectAsState()
를 사용하면 Flow
를 직접 launch
해서 collect
할 필요 없이, 자동으로 Flow
를 구독하며 현재 값을 Compose 상태로 변환해 줍니다.class MainViewModel : ViewModel() {
private val _uiState = MutableStateFlow(MyUiState())
val uiState: StateFlow<MyUiState> = _uiState.asStateFlow()
fun updateSomething(newValue: String) {
_uiState.update { currentState ->
currentState.copy(something = newValue)
}
}
}
MutableStateFlow
: 내부에서 변경 가능한 상태 정의StateFlow로
: 외부에는 읽기 전용@Composable
fun MainScreen(viewModel: MainViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsState()
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column {
Text(text = "Current value: ${uiState.something}")
Button(onClick = { viewModel.updateSomething("New Value") }) {
Text("Update Value")
}
if (uiState.isLoading) {
CircularProgressIndicator()
}
uiState.error?.let { error ->
Text(
text = error,
color = MaterialTheme.colorScheme.error
)
}
}
}
}
uiState
: Flow를 State로 변환이제 uiState는 State 타입으로,
viewModel.uiState의 값이 변경될 때마다 이 컴포저블이 재구성됩니다
@Composable
fun MainScreen(viewModel: MainViewModel = viewModel()) {
var uiState by remember { mutableStateOf(MyUiState()) }
LaunchedEffect(viewModel) {
viewModel.uiState.collect { newState ->
uiState = newState
}
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column {
Text(text = "Current value: ${uiState.something}")
Button(onClick = { viewModel.updateSomething("New Value") }) {
Text("Update Value")
}
if (uiState.isLoading) {
CircularProgressIndicator()
}
uiState.error?.let { error ->
Text(
text = error,
color = MaterialTheme.colorScheme.error
)
}
}
}
}
val state1 by flow1.collectAsState()
val state2 by flow2.collectAsState()
val state3 by flow3.collectAsState()
var state1 by remember { mutableStateOf(initialValue1) }
var state2 by remember { mutableStateOf(initialValue2) }
var state3 by remember { mutableStateOf(initialValue3) }
LaunchedEffect(Unit) {
launch { flow1.collect { state1 = it } }
launch { flow2.collect { state2 = it } }
launch { flow3.collect { state3 = it } }
}
val state by flow.collectAsState(initial = initialValue)
var state by remember { mutableStateOf(initialValue) }
LaunchedEffect(Unit) {
flow.collect { state = it }
}
LaunchedEffect(Unit) {
try {
flow.collect { state = it }
} catch (e: Exception) {
// error
}
}
메모리 사용: 두 방식 모두 비슷한 메모리 사용량을 보이지만 collectAsState()
는 내부적으로 최적화되어 있어 약간 더 효율적일 수 있습니다.
재구성 효율성: collectAsState()
는 Compose의 상태 시스템과 긴밀하게 통합되어 변경된 부분만 효율적으로 재구성합니다.
개발자 효율성: collectAsState()
를 사용하면 개발자는 보일러플레이트 코드를 줄이고 핵심 비즈니스 로직에 집중할 수 있습니다.
collectAsState(context = ...)
로도 가능합니다)collectAsState()
는 제트팩 컴포즈에서 Flow
를 관찰하는 가장 간결하고 효율적인 방법입니다. 기존의 방식과 비교했을 때 코드가 더 간결해지고 생명주기 관리가 자동으로 이루어지며 여러 Flow
를 쉽게 처리할 수 있다는 장점이 있습니다.
그러나 특별한 요구사항이 있는 경우 전통적인 방식을 사용하여 Flow
수집에 대한 더 세밀한 제어가 가능합니다. 각 방식의 장단점을 이해하고 상황에 맞게 선택하는 것이 중요 한 것 같습니다.
좋은글이네요. 조금 제경험을 이야기하자면
아래와 같이 통으로 뷰모델을 감시하는 경우는 실무에선 없었던거 같아요.
그리고 아래의 패턴이 많이 사용되는데 이 경우도 직접 View에 uiState를 넘기는것보다는 DTO를 만들어서 넘기는 식으로 구현했어요.
val uiState by viewModel.uiState.collectAsState()
요런식으로..