우선 라이브러리를 추가합니다
[libs.version.toml]
[versions]
//...
viewModelVersion = "2.8.7"
composeVersion = "1.7.2" // 현재 사용 중인 compose 버전
[libraries]
//...
androidx-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref="viewModelVersion"}
androidx-livedata-compose = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "composeVersion" }
[build.gradle] app
implementation(libs.androidx.viewmodel.compose)
implementation(libs.androidx.livedata.compose)
viewModel을 상속받는 클래스를 하나 만들어줍니다
class ToDoViewModel: ViewModel() {
val (text, setText) =
androidx.compose.runtime.mutableStateOf("")
}
근데 여기서 val (text, setText)에 레드라인이 생기고 아래와 같은 메시지가 떠요
구조 분해 선언은 지역 변수나 지역 값에 대해서만 사용할 수 있어요 즉 , 구조 분해 선언(destructuring declarations)은 한 클래스 내에 종속될 수 없기 때문에 위와 같은 에러가 뜹니다
그래서 이렇게 바꿔줍니다
class ToDoViewModel: ViewModel() {
val text = mutableStateOf("")
}
ViewModel을 param으로 받는 composable func에서 viewModel의 값을 가져오거나, 변경해야한다면 아래와 같이 쓸 수 있습니다
@Composable
fun Test(viewModel: ToDoViewModel = viewModel()) {
//...
ToDoInput(
text = viewModel.text.value,
onTextChange = {
viewModel.text.value = it
},
onSubmit = onSubmit
)
}
@Composable
fun ToDoInput(
text: String,
onTextChange: (String) -> Unit,
onSubmit: (String) -> Unit
) {
Row(modifier = Modifier.padding(8.dp)) {
OutlinedTextField(
value = text,
onValueChange = onTextChange,
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.size(8.dp))
Button(onClick = {
onSubmit(text)
}) {
Text("입력")
}
}
}
viewModel로 상태를 옮길 수 있고, 상태를 옮기는 경우에는 remember를 쓰지 않습니다
(remember는 composable 함수의 생명주기에 맞춰 동작하기 때문에)
그리고, ViewModel을 사용하여 상태를 관리하면 상태가 생명주기와 더 잘 분리되고, 당연히 구성 변경시에도 상태를 유지합니다
이제 위에서 했던 예제들을 LiveData로 변경해보겠습니다
먼저, viewmodel을 상속받은 곳에서 아래와 같이 변경을 해줍니다
텍스트는 liveData화 시키고, 텍스트의 값을 변경하는 것을 viewModel로 가져옵니다
val text = MutableLiveData("")
val setText : (String) -> Unit = {
text.value = it
}
그리고 composable 함수에서 text의 값을 liveData를 쓰는 게 아니라, 그 값을 상태로 변환해서 가져옵니다
@Composable
fun Test(viewModel: ToDoViewModel = viewModel()) {
//...
ToDoInput(
text = viewModel.text.observeAsState("").value,
onTextChange = {
viewModel.setText
},
onSubmit = onSubmit
)
}
상태로 변환해서 사용하는 이유 ? -> Compose의 핵심 기능이 상태 기반 프레임워크이기 때문에, Compose 내의 값들은 당연히 상태를 기반으로 해야 Compose를 사용하는 의미가 있다..!
그리고 LiveData를 사용할 때 주의할 점인 viewModel 외부에서 MutableLiveData에 직접 접근해서 수정하지 못하도록 해야하기 때문에 위의 viewModel을 다시 수정합니다
val _text = MutableLiveData("")
val text: LiveData<String> get() = _text
val setText : (String) -> Unit = {
_text.value = it
}
만약 ViewModel에서 리스트를 관리하고 싶다면 Livedata로 선언하고 이걸 state로 변환하는 것은 추천하지 않는 방법이라고 합니다
그 이유는, observeAsState의 원형을 보면 DisposableEffect에서 this(LiveData)를 받아서 호출하게 되는데
이 때 LiveData의 Observer는 리스트의 참조가 변경이 되어야 이 observer가 호출되기 때문에 매번 리스트 전체를 갱신해줘야 호출이 된다 -> 아주 👎🏻 ..
@Composable
fun <R, T : R> LiveData<T>.observeAsState(initial: R): State<R> {
val lifecycleOwner = LocalLifecycleOwner.current
val state = remember {
@Suppress("UNCHECKED_CAST") /* Initialized values of a LiveData<T> must be a T */
mutableStateOf(if (isInitialized) value as T else initial)
}
DisposableEffect(this, lifecycleOwner) {
val observer = Observer<T> { state.value = it }
observe(lifecycleOwner, observer)
onDispose { removeObserver(observer) }
}
return state
}
그래서 compose에서는 mutableStateListOf를 많이 사용한다고 합니다
'🤖 Compose' 카테고리의 다른 글
[Compose] compositionLocal (0) | 2025.01.21 |
---|---|
[Compose] Compose 주의점 (0) | 2025.01.17 |
[Compose] SideEffect (0) | 2025.01.16 |
[Compose] Animation (0) | 2025.01.08 |
[Compose] State, StateHoisting (0) | 2025.01.07 |