ViewModel 사용하기 - Jetpack Compose

Shawn Kang·2023년 1월 8일
0

Jetpack Compose

목록 보기
2/6
post-thumbnail

개요

ViewModel을 Compose에 적용하려고 레퍼런스를 찾아봤는데 내용이 많지 않았다. 하루 정도 삽질하고 나서 알아낸 Compose에서 ViewModel 적용하는 방법을 공유해보고자 한다.
(이 글은 Material 3를 기준으로 작성되어 있다. 참고 바란다.)


To-do

해야 할 것은 아래와 같다:

  • ViewModel 클래스 구현
  • ViewModelFactory 클래스 구현
  • View에 ViewModel 붙이기

참고로 ViewModelFactory는 ViewModel을 단일 객체로 생성해 사용하기 위해(= 싱글톤 패턴이라나) 구현하는 클래스다.


구현

ViewModel 클래스 구현

적당한 디렉토리에 MainViewModel.kt 파일을 만들고 아래와 같이 클래스를 선언한다.

class MainViewModel() : ViewModel() {
	// To do
}

그리고 MainView에서 표시되어야 할 내용을 담을 변수를 선언한다. 단, 변수의 값을 바꾸는 행위는 반드시 ViewModel 내에서 이루어져야 한다. 다시 말해 변수는 private로 선언하여 ViewModel 내에서만 접근 가능하도록 설정하고, View는 별도로 작성된 Setter를 통해서만 변수 값을 수정할 수 있도록 구현해야 한다는 말이다. 아래 예시를 참고하자:

class MainViewModel() : ViewModel() {
    private val _counter = mutableStateOf(0)
    val counter: State<Int> = _counter

    fun increaseCounter() {
        _counter.value += 1
    }
    fun decreaseCounter() {
        _counter.value -= 1
    }
}

_counterprivate로 선언하여 외부에서의 접근을 막았다. 그리고 counter 변수를 통해 Getter를, increaseCounter()decreaseCounter()로 Setter(엄밀히 말하자면 Setter는 아니지만 어쨌든 값을 조작하는)를 구현했다. View는 이 세 가지 요소를 사용하여 ViewModel에 속한 변수의 값을 읽고 쓰게 된다.

ViewModelFactory 클래스 구현

ViewModel을 싱글톤으로 쓰기 위해 ViewModelFactory 클래스를 구현해야 한다. 그렇게 하는 이유로는 여러 개의 ViewModel 객체가 존재할 때, 객체들이 동시에 같은 데이터에 접근을 시도할 경우 발생할 수 있는 예외 상황을 방지하기 위해서라고 추측한다.

class MainViewModelFactory() : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return MainViewModel() as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

이 코드는 수정하지 말고, 필요한 경우 ViewModel 이름만 바꾸어 쓸 수 있도록 하자.

아쉽게도 현재의 내 Kotlin 및 Compose 숙련 수준에서는 코드를 이렇게 쓰는 이유를 잘 이해할 수 없다. 나중에 시간이 되면 공부해 볼 필요가 있겠다.

View에 ViewModel 붙이기

먼저 ViewModel을 MainActivity에서 인스턴스로 선언해 줄 필요가 있다. MainActivity.kt 파일을 열고 setContent {} 함수 내에서 ViewModel을 선언하도록 하자. 그 후에는 선언한 ViewModel을 필요한 Composable까지 매개 변수로 쭉 내려주면 된다. 아래와 같이:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
        	// ViewModel 선언
            val mainViewModel = ViewModelProvider(
                this,
                MainViewModelFactory()
            )[MainViewModel::class.java]

            ComposeTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                	//선언한 ViewModel을 매개 변수를 통해 전달하기
                    MainView(mainViewModel)
                }
            }
        }
    }
}

내가 작성한 MainView는 대충 아래와 같다. 이건 화면에 카운터를 보여주고, 증가 버튼을 누르면 카운터가 1 증가, 감소 버튼을 누르면 카운터가 1 감소하는 간단한 예제다:

@Composable
fun MainView(viewModel: MainViewModel) {
    Column(
        verticalArrangement = Arrangement.spacedBy(12.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier
            .fillMaxSize()
            .padding(20.dp)
    ) {
    	// 카운터 값을 표시하는 Text Composable
        Text(
            text = viewModel.counter.value.toString(),
            style = MaterialTheme.typography.titleMedium
        )
        // 증가 버튼
        Button(onClick = { viewModel.increaseCounter() }) {
            Icon(
                imageVector = Icons.Default.KeyboardArrowUp,
                contentDescription = "Increase counter"
            )
        }
        // 감소 버튼
        Button(onClick = { viewModel.decreaseCounter() }) {
            Icon(
                imageVector = Icons.Default.KeyboardArrowDown,
                contentDescription = "Decrease counter"
            )
        }
    }
}


결과


잘 작동한다. 굿.

profile
i meant to be

0개의 댓글