이번 글에서는 Hilt를 이용하여 MVVM아키텍쳐 구조의 간단한 노트를 구현해보고자 합니다.
혹시 MVVM패턴에 익숙하지 않다면 아래 글에 자세히 설명되어 있습니다
https://velog.io/@201/mvvmarchitecture
안드로이드 MVVM패턴을 통해 앱을 만들다보면 종속성 처리가 상당히 까다롭다고 많이 느끼게 됩니다. 예를 들어 아래와 같은 viewModel을 사용한다 가정해봅시다
class NoteViewModel : ViewModel() {
private val _text = MutableLiveData<String>()
private val text : LiveData<String>
get() = _text
}
NoteViewModel을 activity 혹은 fragment에서 사용하는 방법은 두가지가 있습니다.
1) viewModelFactory
var viewModel = ViewModelProvider(this).get(NoteViewModel::class.java)
2) viewModel ktx와 activity ktx 사용
private val viewModel : NoteViewModel by viewModels()
MVVM구조에 따르면 viewModel은 repository를 사용하기 때문에 파라미터로 받아야 합니다. 즉 종속성이 있어야 하는 것이죠. 이를 위해서는 커스텀한 viewModelFactory를 만들어야 합니다.
class Factory(private val repository: Repository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return NoteViewModel(repository) as T
}
}
하지만 커스텀한 viewModel은 boiler-plate 코드를 증가시키고 관리해야하는 클래스의 수를 늘립니다. 이는 구현할 때 뿐만아니라 나중에 관리 및 유지보수를 할 때도 좋지 않습니다.
이때 Hilt를 사용하면 깔끔하게 해결할 수 있습니다. 커스텀 viewModelFactory와 같은 종속성 관련 클래스를 Hilt가 대신 만들어주기 때문입니다.
이제 Hilt를 사용해봅시다. 먼저 gradle 세팅이 필요합니다. 안드로이드 개발 문서에 자세히 나타나 있습니다
https://developer.android.com/training/dependency-injection/hilt-android?hl=ko#setup
먼저 DataBase와 DAO클래스를 Hilt로 만들어봅시다
@Module
@InstallIn(SingletonComponent::class)
class DiModule {
@Singleton
@Provides
fun provideNoteDatabase(@ApplicationContext context: Context) : NoteDatabase {
return Room
.databaseBuilder(
context,
NoteDatabase::class.java,
NoteDatabase.DATABASE_NAME)
.build()
}
@Singleton
@Provides
fun provideNoteDAO(noteDB: NoteDatabase): NoteDAO {
return noteDB.noteDao()
}
}
각 어노테이션은 아래와 같습니다
@Module : Hilt 모듈임을 나타냅니다
@InstallIn : Hilt 모듈을 설치할 안드로이드 컴포넌트를 나타냅니다. SingletonComponent는 Application에 설치한다는 의미 입니다
@Singleton : Hilt로 제공할 컴포넌트가 Application 범위에 존재합니다. 즉 앱이 꺼질 때까지 유효합니다.
@Provides : 함수가 인스턴스를 제공함을 Hilt에 알려주는 역할을 합니다.
즉, 정리해보면 싱글톤 객체로 database와 dao가 제공됨을 알 수 있습니다.
또한 @ApplicationContext를 통해 activity나 fragmet로부터 context를 주입하지 않아도 바로 사용할 수 있습니다.
@Module
@InstallIn(SingletonComponent::class)
class DiModule {
@Singleton
@Provides
fun provideNoteRepository(
noteDAO: NoteDAO
) : NoteRepository{
return NoteRepository(noteDAO)
}
}
repository에서는 데이터 저장을 해야하기 때문에 noteDAO를 변수로 가져야 합니다.
@HiltViewModel
class NoteViewModel @Inject constructor(
private val repository: NoteRepository
): ViewModel() {
private val _text = MutableLiveData<String>()
private val text : LiveData<String>
get() = _text
}
viewModel에서는 새로운 어노테이션 두 개가 쓰입니다
@HiltViewModel : Hilt에게 해당 컴포넌트가 ViewModel임을 알려줍니다
@Inject : 해당 컴포넌트를 생성하는데 어떤 다른 종속성이 필요한지 알려줍니다. 위 예시에서는 Hilt가 NoteRepository를 제공하는 법을 알기 때문에 사용가능합니다
위와 같이 Hilt를 이용하여 viewModel을 만들면 커스텀 ViewModelFactory를 만들지 않고 viewModel을 사용할 수 있습니다.
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val noteViewModel : NoteViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
activity에서는 @AndroidEntryPoint 어노테이션을 사용합니다. 이 어노테이션은 Hilt가 제공하는 종속항목을 사용하기 위해서 필요합니다. 이제 MainActivity에서 NoteViewModel을 Hilt로 부터 제공받기 때문에 종속성이 해결되었음을 알 수 있습니다.
@HiltAndroidApp
class MainApplication : Application() {
}
마지막으로 @HiltAndroidApp을 통해 Hilt가 적용될 어플리케이션 클래스를 설정해줘야 합니다.
Hilt와 관련된 코드는 모두 살펴보았습니다. 전체 코드는 아래 github 레포에 있습니다
https://github.com/20Han/hilt_example
Hilt를 처음쓰면 새로운 어노테이션도 많고 복잡합니다. 하지만 많은 boiler-plate 코드를 없애주고 종속성 주입을 쉽게 할 수 있습니다. 따라서 충분히 배울 만한 가치가 있으며 개발중인 앱에 꼭 적용해보면 좋을 것 같습니다
[참고자료]
https://developer.android.com/training/dependency-injection/hilt-android?hl=ko
https://developer.android.com/training/dependency-injection/hilt-jetpack