Compose는 기본적으로 수동적으로 데이터의 상태를 받아서 상태변경이 감지되면 compose function을 다시호출하여 변경된 부분만 업데이트하는(recompose) 방식으로 동작하도록 설계가 되어 있습니다. 따라서 변경된 상태 데이터를 compose function에 파라미터로 전달해서 compose function이 다시 호출되도록 처리해주어야 합니다.
ViewModel을 사용한다면, 모든 상태의 원본은 ViewModel에서 관리를 하고 compose function에서는 compose state를 통해서 ViewModel 의 상태데이터의 변경을 통보받도록 구성하여야 합니다.
@Composable
private fun MyScreenContent(
uiState: MyUiState,
onToggleClicked: (Boolean) -> Unit
) {
Column {
if (uiState.error != null) {
ErrorMessageCard(message = uiState.error.message ?: "Unknown error")
}
ToggleButton(uiState.isToggleOn) { toggleValue ->
onToggleClicked(toggleValue)
}
}
}
@Composable
fun ErrorMessageCard(
message: String,
contentPadding: Dp = 8.dp,
icon: ImageVector = Icons.Default.Error,
color: Color = MaterialTheme.colors.error
) {
Card(
modifier = Modifier
.fillMaxWidth()
) {
Row(
modifier = Modifier.padding(contentPadding),
verticalAlignment = Alignment.CenterVertically
) {
Image(
imageVector = icon,
contentDescription = null,
colorFilter = ColorFilter.tint(color)
)
Text(
modifier = Modifier.padding(8.dp),
text = message,
color = color
)
}
}
}
@Composable
private fun ToggleButton(
isOn: Boolean,
onClicked: (Boolean) -> Unit
) {
Button(onClick = { onClicked(!isOn) }) {
Text(text = if (isOn) "Off" else "On")
}
}
class MyViewModel (private val repository: MyRepository): ViewModel() {
var uiState by mutableStateOf(MyUiState())
private set
fun onToggleClicked(toggleValue: Boolean) {
viewModelScope.launch {
repository.updateToggle(toggleValue)
.onFailure { th ->
uiState = uiState.copy(error = th)
}
.onSuccess {
uiState = uiState.copy(isToggleOn = true)
}
}
}
}
data class MyUiState(
val error: Throwable? = null,
val isToggleOn: Boolean = false
)
class MyRepository {
suspend fun updateToggle(value: Boolean): Result<Boolean> = withContext(Dispatchers.IO) {
Result.success(true)
}
}
이걸 state hoisting이라고 하는데, compose에서 아주 핵심이 되는 부분이므로 잘 이해해 두어야 부작용이 없게 됩니다.
아래 개발자 문서를 꼼꼼히 읽어보세요.
https://developer.android.com/jetpack/compose/state
Edit: 버튼을 눌러서 토글 상태를 변경하는 간단한 샘플로 예제를 변경했습니다.