Development record of developer who study hard everyday.

레이블이 Dagger2 migration인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Dagger2 migration인 게시물을 표시합니다. 모든 게시물 표시
, , ,

Dagger2에서 Hilt로 이전하기(migration)

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에 비하면 천국이였다.


Share:
Read More