Hilt와 함께 ViewModel 사용하기 - Jetpack Compose

Shawn Kang·2024년 9월 29일
0

Jetpack Compose

목록 보기
5/5
post-thumbnail

개요

더 정확히 말하자면, 디자인 패턴 중 MVVM(Model-View-ViewModel) 패턴을 구현하는 방법이다. Hilt를 끼지 않고 ViewModel을 쓸 수 있는 방법도 있는 것으로 알고 있으나, Hilt를 쓰는 게 생산성이나 정신건강 측면에서 이로울 것이다.

그리고 이 게시글은 KSP로 Hilt 적용하기 - Jetpack Compose 게시글에서의 요구사항이 충족되지 않으면 적용할 수 없다. 프로젝트에 아직 Hilt를 적용하지 않았다면, Hilt부터 적용하고 나서 이 게시글을 보도록 하자.

구현

프로젝트 구조

프로젝트는 아래와 같은 구조를 가진다:

/프로젝트 루트/view/SampleView.kt
/프로젝트 루트/viewmodel/SampleViewModel.kt

'SampleView.kt' 파일은 구체적인 UI 구현이 담긴 페이지이고, 'SampleViewModel.kt' 파일은 'SampleView'에 표시될 데이터를 관리하는 뷰 모델이다. 이런 식으로 각각의 페이지마다 뷰 모델을 하나씩 만들어주면 된다.

ViewModel 작성

아래와 같이 ViewModel을 구현하자:

@HiltViewModel
class SampleViewModel @Inject constructor(
	// 필요한 모델 추가
) : ViewModel() {
	// 변수
    private val _sampleText = mutableStateOf<String>("Sample text")
    
    // Getter
    val sampleText: State<String> = _sampleText
    
    // Setter
    fun setSampleText(newValue: String) {
    	_sampleText.value = newValue
    }
}

@HiltViewModel

이 어노테이션은 Hilt에게 내가 이 뷰 모델을 Hilt를 통해 사용할 거라고 알려주는 역할을 한다. 추측컨대, 뷰 모델을 여러 개 만들면 문제가 생길 수 있으니 이 어노테이션을 지정해서 뷰 모델이 여러 번 요청되어도 싱글톤으로 생성된 단 하나의 뷰 모델만 전달하도록 제한하는 역할을 하는 게 아닌가 싶다.

@Inject constructor()

필요하다면, 데이터베이스 등 접근해야 하는 모델을 이 생성자에 넣어주면 된다. Android의 로컬 데이터베이스 서비스인 Room을 예시로 들 수 있겠다. 만약 Room을 사용한다면, 아래처럼 생성자가 구현될 것이다:

class SampleViewModel @Inject constructor(
	private val UserRepository: userRepository
) : ViewModel() {
	// ...
}

위 코드에서 주의해서 볼 부분은, 뷰 모델을 통해 누군가가 외부 모델에 마음대로 접근하는 것을 막기 위해, 모델에 private 키워드를 달아 주었다는 점이다.

한편, 우리는 모델의 데이터를 안전히 관리하기 위해 뷰 모델을 사용하는 것인 만큼, UI에 표시될 데이터도 엄격하게 선언하여 관리할 필요가 있다. 위 코드를 보면 3가지 요소가 있는데, Raw 변수, Getter와 Setter가 그것이다.

변수

private val _변수 이름 = mutableStateOf<변수 타입>(변수 값)

변수는 위처럼 선언하는데, private 접근 제한자를 앞에 달아서 뷰 모델 외부에서 무단으로 변수 값에 접근할 수 없도록 막아준다. 그리고 mutableStateOf() 함수를 통해 상태(State)로 선언하여, 값이 바뀌었을 때 컴포저블이 추적하여 UI를 수정할 수 있게 한다.

추가로, IntFloat와 같은 자료형은 mutableIntStateOf(), mutableFloatStateOf()와 같은 자체적인 상태 함수도 있으니 참고 바람.

var _변수 이름 by mutableStateOf(변수 값)
	private set

위 방법도 가능하다. 이 코드는 변수 선언과 Getter를 합친 것이다. Setter의 경우에는 private 접근 제한자를 달아, 역시 뷰 모델 외부에서 마음대로 접근할 수 없도록 막았다. 그래서 만약 Setter가 필요하다면, 아래 적혀있는 대로 별도의 Setter 선언이 필요하다.

Getter

val 변수 이름: State<변수 타입> = _변수 이름

Getter는 위처럼 선언한다. 처음에, 우리는 mutableStateOf() 함수를 통해 MutableState 타입으로 변수를 선언했었다. 하지만 이 타입은 Mutable하기에 값의 변경이 자유자재로 가능하다. 따라서 변수를 MutableState가 아닌 값의 읽기만 가능한 State로 선언하여, 뷰 모델 외부에서는 값을 읽을 수만 있도록 제한한다.

Setter

fun setSampleText(newValue: String) {
	_sampleText.value = newValue
}

Setter는 위처럼 선언한다. 너무 뻔해서 더 할 얘기는 없는 코드.

View 작성

본인 입맛과 프로젝트의 설계에 맞게 아래와 같이 View를 구현하자:

@Composable
fun SampleView(
	viewModel: SampleViewModel = hiltViewModel() // ViewModel 주입
) {
	Text(text = viewModel.sampleText.value) // ViewModel의 데이터인 'sampleText'를 가져다 쓰기
}

코드의 3번째 줄 viewModel: SampleViewModel = hiltViewModel()을 보자. 이 코드를 간단히 설명하면, Hilt에게 "Hilt야, 내가 hiltViewModel()을 호출했으니 너는 내가 viewModel: SampleViewModel 코드를 통해 지정한 SampleViewModel을 의존성 주입을 통해 SampleView에 가져다줘!"라고 하는 것과 똑같다.

hiltViewModel() 함수를 뷰의 매개변수에 사용하면, Hilt는 알아서 @HiltViewModel 어노테이션이 달린 뷰 모델을 개발자가 요청한 뷰에 주입해주는 것이다.

필요한 데이터는 viewModel.필요한 변수 및 메소드의 형식으로 접근하여 사용하면 된다.

profile
i meant to be

0개의 댓글