Dagger2에서 Hilt로 이전하기
회사프로젝트에 쓰이는 베이스소스가 있다.
지금 다니는 회사는 웹앱이 주 업무라서 베이스소스에 푸시알림이나 권한 등 기본적인 기능이 미리 세팅이 되어있다.
MVVM 패턴으로 구현이 되어있고 종속성주입에는 Dagger2 라이브러리가 사용된다.
하지만 이제 Dagger2는 지는 해이고 Hilt를 대부분 사용하기 때문에 이번기회에 migration하기로 했다.
1. Hilt 설정
buildscript {
ext.hilt_version = '2.44'
repositories {
...
}
dependencies {
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
👆 프로젝트수준 build.gradle에 위와 같이 hilt를 설정한다.
apply plugin: 'com.google.dagger.hilt.android'
android {
}
kapt {
correctErrorTypes = true
}
dependencies {
//hilt
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}
👆 앱수준 build.gradle에 위와 같이 hilt를 설정한다.
2. Application 수준에서 이전
가장 큰 Scope인 Application 수준에서의 이전을 시작한다.
먼저, Application 객체에 가서 Hilt를 사용한다고 알려주자
@Singleton
@HiltAndroidApp
class MainApplication @Inject constructor() : BaseApplication() {
// private lateinit var applicationInjector: AppComponent
override val configPath = "config.json"
override fun onCreate() {
// applicationInjector = DaggerAppComponent.factory().create(this)
super.onCreate()
}
// override fun applicationInjector(): AndroidInjector<out DaggerApplication> = applicationInjector
// fun appComponent() = applicationInjector
}
👆 Dagger와 관련된 코드는 다 없애준다.
솔직히, 나도 Dagger2를 사용해본적은 없기때문에 주석처리한 코드가 정확히 무슨 역할을 하는지는 잘 모른다.
하지만 종속성 주입을 위한 컴포넌트를 만드는 것이라는 것은 안다.
그리고 Hilt는 컴포넌트를 자동으로 생성해주기 때문에 저런 코드가 다 필요가 없다.
abstract class BaseApplication : /*Dagger*/Application() {
abstract val configPath: String
override fun onCreate() {
super.onCreate()
Config.init(this, configPath)
}
}
👆 BaseApplication 클래스에서도 원래 DaggerApplication()을 상속했지만 없애주고 Application()을 상속하도록한다.
이제 AppComponent에 설치되어있던 모듈을 SingletonComponent에 설치해주자.
SingletonComponent는 Hilt에서 Application Scope에서 종속성 주입을 하기위해 만드는 Container를 말한다.
@Singleton
@Component(modules = [
AndroidInjectionModule::class,
ActivityModule::class,
FragmentModule::class,
ServiceModule::class,
ViewModelModule::class,
AppModule::class,
AppBindModule::class,
NetworkModule::class
])
interface AppComponent : AndroidInjector<MainApplication> {
@Component.Factory
interface Factory {
fun create(@BindsInstance application: MainApplication): AppComponent
}
override fun inject(app: MainApplication)
}
👆 Dagger2에 사용되는 AppComponent의 코드이다.
AppComponent에 총 8개의 모듈이 설치가 되어있다.
이 모듈 클래스에 가서 @InstallIn(SingletonComponent::class) 어노테이션을 등록한다.
(AndroidInjectionModule은 Dagger2 기본 모듈이라 무시;;)
- ActivityModule
@InstallIn(SingletonComponent::class)
@Module
abstract class ActivityModule {
@ContributesAndroidInjector
abstract fun contributeMainActivity(): MainActivity
}
- FragmentModule
@InstallIn(SingletonComponent::class)
@Module
abstract class FragmentModule {
}
-ServiceModule
@InstallIn(SingletonComponent::class)
@Module
abstract class ServiceModule {
@ContributesAndroidInjector
abstract fun contributeFCMMessagingService(): FCMMessagingService
}
-ViewModelModule
@InstallIn(SingletonComponent::class)
@Module(includes = [
ViewModelFactoryModule::class
])
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(MainVM::class)
abstract fun bindMainVM(mainVM: MainVM): ViewModel
}
@MustBeDocumented
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
-AppModule
@InstallIn(SingletonComponent::class)
@Module(includes = [
ApiServiceModule::class,
RepositoryModule::class
])
class AppModule {
}
-AppBindModule
@InstallIn(SingletonComponent::class)
@Module(includes = [
BaseBindModule::class
])
abstract class AppBindModule {
@Binds
abstract fun bindAppApplication(application: MainApplication): Application
@Binds
@IntoSet
abstract fun provideTokenInterceptor(tokenInterceptor: TokenInterceptor): Interceptor
}
-NetworkModule
@Module
@InstallIn(SingletonComponent::class)
class NetworkModule {
@Provides
fun provideClient(interceptors: Set<@JvmSuppressWildcards Interceptor>): OkHttpClient {
val client = OkHttpClient.Builder().apply {
connectTimeout(Config.INSTANCE.connectTimeOut, TimeUnit.MILLISECONDS)
readTimeout(Config.INSTANCE.readTimeOut, TimeUnit.MILLISECONDS)
writeTimeout(Config.INSTANCE.writeTimeOut, TimeUnit.MILLISECONDS)
if (interceptors.isNotEmpty()) {
interceptors.forEach {
addInterceptor(it)
}
}
}
val logging = HttpLoggingInterceptor().apply {
setLevel(HttpLoggingInterceptor.Level.BODY)
}
client.addNetworkInterceptor(logging)
return client.build()
}
@Provides
fun provideRetrofit(client: OkHttpClient): Retrofit {
return Retrofit.Builder().apply {
baseUrl(Config.INSTANCE.apiUrl)
client(client)
}.apply {
addCallAdapterFactory(RxJava2CallAdapterFactory.create())
addConverterFactory(GsonConverterFactory.create())
}.build()
}
}
👉 이렇게 AppComponent에 있는 모듈들을 전부 SingletonComponent에 설치해주었다.
그리고 Module에 설치된 Module이 또 있다;;
난 dagger2를 몰라서 왜 이렇게 만든지는 모르지만 아무튼 AppComponent와 관련된 Module은 싹다 SingletonComponent에 InstallIn해준다.
-ViewModelFactoryModule
@InstallIn(SingletonComponent::class)
@Module
abstract class ViewModelFactoryModule {
@Binds
abstract fun bindViewModelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory
}
-ApiServiceModule
@InstallIn(SingletonComponent::class)
@Module
class ApiServiceModule {
@Singleton
@Provides
fun provideCommonService(retrofit: Retrofit): CommonService = retrofit.create(CommonService::class.java)
}
-RepositoryModule
@InstallIn(SingletonComponent::class)
@Module
class RepositoryModule {
@Singleton
@Provides
fun providePreferenceRepository(application: MainApplication, @ApplicationContext context: Context): PreferenceRepository =
PreferenceRepository(PreferenceManager(application, context))
@Singleton
@Provides
fun provideCommonRepository(service: CommonService): CommonRepository = CommonRepository(service)
}
-BaseBindModule
@Module class BaseBindModule{}
AppComponent에 있는 Module을 전부 hilt가 자동으로 생성해주는 SingletonComponent에 설치했다.
따라서 AppComponent는 더이상 필요가 없다.
AppComponent 클래스를 지운다.
3. Hilt로 ViewModel 주입
힐트를 사용하면 ViewModel을 아주 쉽게 주입할 수 있다.
Hilt로 migration 중인 이 프로젝트에는 MainVM 하나 밖에 없다.
클래스명에 @HiltViewModel 만 적어주면 끝이다.
@HiltViewModel
class MainVM @Inject constructor(
application: MainApplication,
@ApplicationContext context: Context
) : BaseViewModel(application){
// val locationData = LocationLiveData(application)
val locationData = LocationLiveData(context)
}
이제, hilt로 ViewModel을 주입해주기때문에 Dagger2로 ViewModel을 주입하기위해 필요했던 잡다한 Module을 다 제거한다.
ViewModelFactoryModule, DaggerViewModelFactory, ViewModelFactoryModule 다 삭제한다.
이제 프로젝트를 빌드해보면 정상적으로 빌드가된다.
4. Dagger2의 SubComponent 제거
Dagger2에서는 SubComponent도 직접 만들어준다.
Hilt에서는 SubComponent를 자동으로 만들어주기 때문에 이 녀석들을 전부 제거하자.
-ActivityModule 삭제
@InstallIn(SingletonComponent::class)
@Module
abstract class ActivityModule {
@ContributesAndroidInjector
abstract fun contributeMainActivity(): MainActivity
}
-FragmentModule 삭제
@InstallIn(SingletonComponent::class)
@Module
abstract class FragmentModule {
}
-ServiceModule 삭제
@InstallIn(SingletonComponent::class)
@Module
abstract class ServiceModule {
@ContributesAndroidInjector
abstract fun contributeFCMMessagingService(): FCMMessagingService
}
-ViewModelModule 삭제
@InstallIn(SingletonComponent::class)
@Module(includes = [
ViewModelFactoryModule::class
])
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(MainVM::class)
abstract fun bindMainVM(mainVM: MainVM): ViewModel
}
@MustBeDocumented
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
5. Dagger2 라이브러리 삭제
// Dependency Inject - Dagger2
// def dagger_version = "2.45"
// implementation "com.google.dagger:dagger:$dagger_version"
// implementation "com.google.dagger:dagger-android:$dagger_version"
// implementation "com.google.dagger:dagger-android-support:$dagger_version"
// kapt "com.google.dagger:dagger-compiler:$dagger_version"
// kapt "com.google.dagger:dagger-android-processor:$dagger_version"
끝났다!!
Dagger2 -> Hilt 마이그레이션을 해보니 Hilt가 얼마나 좋은 라이브러리인지 깨닫게되었다.
Hilt 공부할 때는 최초설정하는 코드가 많고 어렵다고 느껴졌는데 Dagger2에 비하면 천국이였다.