🤖 Compose

[Compose] viewModel , LiveData

콩드로이드 2025. 1. 21. 15:30

 

우선 라이브러리를 추가합니다

[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