의존성 주입으로 ViewModel 깔끔하게 쓰기 - Jetpack Compose

Shawn Kang·2023년 1월 9일
1

Jetpack Compose

목록 보기
4/6
post-thumbnail

개요

지금까지의 내 방식대로 ViewModel을 쓸 경우 발생하는 큰 문제점이 하나 있다.

나는 앱 진입점인 최상단의 MainActivity.kt에서 ViewModel을 선언하고, 그걸 매개 변수로 쭉 내려주었다. 이런 방식은 앱의 네비게이션 그래프가 복잡해질 경우 코드를 더럽게 만드는 주 원인이 된다. 예를 들어보자:

위와 같은 네비게이션 그래프에서, SubPage1에 딸린 하위 4개 페이지의 ViewModel을 전달하기 위해서는 아래와 같은 코드가 필요하다:

@Composable
fun MainApp(
	page1_1ViewModel: Page1_1ViewModel,
	page1_2ViewModel: Page1_2ViewModel,
	page1_3ViewModel: Page1_3ViewModel,
	page1_4ViewModel: Page1_4ViewModel
) {
    SubPage1(
    	page1_1ViewModel = Page1_1ViewModel,
        page1_2ViewModel = Page1_2ViewModel,
        page1_3ViewModel = Page1_3ViewModel,
        page1_4ViewModel = Page1_4ViewModel
    )
}

@Composable
fun SubPage1(
	page1_1ViewModel: Page1_1ViewModel,
	page1_2ViewModel: Page1_2ViewModel,
	page1_3ViewModel: Page1_3ViewModel,
	page1_4ViewModel: Page1_4ViewModel
) {
    Page1_1(page1_1ViewModel)
    Page1_2(page1_2ViewModel)
    Page1_3(page1_3ViewModel)
    Page1_4(page1_4ViewModel)
}

딱 봐도 코드가 아주 더럽다. 이런 상황을 해결하기 위한 방법으로 의존성 주입(Dependency injection)이라는 개념을 적용한 Hilt 라이브러리를 활용해 ViewModel을 깔끔하게 가져다 쓸 수가 있다.


To do

Hilt 라이브러리를 활용해 각각의 View에 ViewModel을 주입하기 위해서는 아래와 같은 절차를 거쳐야 한다:

  • build.gradle 수정
  • ViewModel에 어노테이션 달기
  • View에 HiltViewModel 적용


구현

build.gradle 수정

가장 먼저 모듈 수준의 build.gradle 파일에 다음 구문을 추가한다:

dependencies {
	// 이외 생략
    implementation "androidx.navigation:navigation-compose:2.5.3"
    implementation "androidx.hilt:hilt-navigation-compose:1.0.0"
}
  • "androidx.navigation:navigation-compose:2.5.3"은 네비게이션 관련 기능을 위한 라이브러리이고
  • "androidx.hilt:hilt-navigation-compose:1.0.0"는 의존성 주입을 위한 라이브러리이다.

둘 다 추가했으면 Gradle Sync를 진행하자.

ViewModel에 어노테이션 달기

이전에 만들어두었던 ViewModel에 두 가지 수정 사항을 반영해주어야 한다:

  • ViewModel 클래스에 @HiltViewModel 어노테이션 달아주기
  • ViewModel 클래스의 생성자 앞에 @Inject constructor 달아주기

이 두 가지 수정 사항을 반영한 코드는 아래와 같다:

// Before
class MainViewModel () : ViewModel() {
    // 대충 ViewModel 코드
}

// After
@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
    // 대충 ViewModel 코드
}

View에 hiltViewModel() 적용

다음으로 ViewModel을 받아먹을 View의 코드를 열고, ViewModel 매개 변수에 기본값으로 hiltViewModel()을 달아주면 끝이다.

// Before
@Composable
fun MainView(viewModel: MainViewModel) {}

// After
@Composable
fun MainView(viewModel: MainViewModel = hiltViewModel()) {}

이렇게까지 하면 끝이다. 이제 MainActivity.kt에서 선언했던 아래의 복잡한 ViewModel 코드를 지워주어도 된다:

val mainViewModel = ViewModelProvider(
    this,
    MainViewModelFactory()
)[MainViewModel::class.java]
            
// 얘 이제 다 지워도 됨


이렇게 하고 앱을 돌려보면 잘 돌아갈 거다.

대충 찾아보니 Hilt는 Dagger라는 라이브러리 기반으로 돌아가는 것 같던데, 어느 정도 수준에서까지 의존성 주입이 가능한지는 좀 알아 볼 필요가 있겠다. 짜증나는 경우 중 하나로 스낵 바 알림을 쓰려면 Scaffold()SnackBarHostState()를 걸어주어야 하는데, 얘도 의존성 주입이 가능하다면 굳이 컴포저블마다 매개 변수로 달아서 내리는 식으로 코드를 지저분하게 쓸 필요가 없지 않을까 하는 생각이다.

profile
i meant to be

0개의 댓글