
개요
기존의 DI를 구성하기위해 구글에서 제공한 Degger2를 사용하여 의존성 주입을 하였지만
높은 학습비용 및 많은 보일러플레이트 코드를 생성한다는 단점때문에
조금더 편한 DI 프레임워크가 나오게된다 이게 Hilt이다.
Hilt는 Dagger를 쉽게 사용할수있도록 도와주는 도구이다.
💡Dagger 은 칼 종류이고 Hilt는 칼집 으로 네이밍 하였다.
프로젝트 세팅
의존성 추가
프로젝트 레벨
buildscript {
repositories {
}
ext{
hiltVersion = '2.38.1'
}
dependencies {
classpath "com.google.dagger:hilt-android-gradle-plugin:${hiltVersion}"
}
}
앱 레벨
plugins {
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
dependencies {
implementation "com.google.dagger:hilt-android:${hiltVersion}"
kapt "com.google.dagger:hilt-compiler:${hiltVersion}"
}
Application 설정
@HiltAndroidApp
class HiltApplication :Application() {
}
@HiltAndroidApp 는 의존성 주입이 가능하도록 Hilt코드를 생성하게 하는 트리거 역활을 수행한다.
이 어노테이션이 붙은 클래스는 어플리케이션 컨테이너로써 앱의 생명주기와 밀접하게 연결된다.
어플리케이션 컨테이너는 앱의 상위 컨테이너로 다른 컨테이너는 이 상위 컨테이너에서 제공하는 클래스에 접근할수있다.
AndroidManifest에 등록.
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SampleApplication"
android:name=".hilt.HiltApplication">
기존 코드
class HiltActivity : AppCompatActivity() {
private lateinit var dataService: DataService
private lateinit var loginService: LoginService
private val btn :Button by lazy { findViewById(R.id.hilt_btn) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hilt)
dataService = DataService()
loginService = LoginService()
btn.setOnClickListener {
loginService.login("id","pwd")
dataService.getUserInfo()
}
}
}
class DataService {
fun getUserInfo() = "user name!"
}
class LoginService {
fun login(id:String,pwd:String) = true
}
activity에서 service에대해 인터턴스를 생성하는 모습이다.
Hilt적용 하기
클래스 필드 주입
@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
@Inject lateinit var dataService: DataService
@Inject lateinit var loginService: LoginService
private val btn :Button by lazy { findViewById(R.id.hilt_btn) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hilt)
btn.setOnClickListener {
loginService.login("id","pwd")
dataService.getUserInfo()
}
}
}
Activity 에서 hilt를 사용하려면 @AndroidEntryPoint 어노테이션을 사용해야한다.
이를 사용하면 해당 activity의 생명주기에 연결된 종속 컨테이너를 생성하고 인스턴스를 주입한다.
만약 프레그먼트에 @AndroidEntryPoint 를 사용하려하면 프레그먼트를 품고있는 엑티비티에도 추가해줘야한다.
@Inject 인스턴스가 붙은 필드들에 주입이된다.
위와같은 형태를 클래스 필드 주입이라고한다.
💡private 필드에는 주입이 되지않는다.
주입 지정
이제 주입해줄 인스턴스를 hilt에서 알게해야한다.
@Singleton
class DataService @Inject constructor(){
fun getUserInfo() = "user name!"
}
class LoginService @Inject constructor() {
fun login(id:String,pwd:String) = true
}
@Inject 어노테이션과 constructor() 통해 hilt에게 DataService,LoginService 에 대한 인스턴스 제공방법을 알리게된다.
constructor()는 생성자로 이 인스턴스를 생성할때 어떤 파라미터(의존)가 필요한지 알리게 되는데
위의 경우는 따로 넘길게 없기때문에 안에 비워져있는걸 볼수있다.
@Singleton 은 해당 인스턴스를 싱글톤으로 제공한다.
주입할 인스턴스에 의존 주입
위에서 본거처럼 아무런 의존관계가없는 서비스들은 constructor() 에 아무것도 넣지않았지만
class LoginService @Inject constructor(private val loginUseCase: LoginUseCase) {
fun login(id:String,pwd:String) = loginUseCase.loginConnection(id,pwd)
}
class LoginUseCase {
fun loginConnection(id:String, pwd:String)= true
}
위와같이 의존이 필요한 경우가 발생하게된다.
이때는 module 을 통해 이 인스턴스에 의존을 주입해야한다.
@InstallIn(ApplicationComponent::class)
@Module
object HiltModule {
@Provides
fun provideLoginUseCase() : LoginUseCase{
return LoginUseCase()
}
}
@Module 은 Hilt에게 모듈임을 알리게된다.
@InstallIn 을 통해 어느 컨테이너에서 결합을 사용하는지를 알리게된다.
예를 들어 Application 컨테이너는 ApplicationComponent
Fragment컨테이너는 FragmentComponent 로 연결한다.
ViewModel 컨테이너는 ViewModelComponent 로 연결한다.
@Provides 는 Hilt에게 이 유형의 인스턴스를 제공해야할때마다 실행되며
LoginUseCase 를 LoginService 에서 필요로 하기때문에
이곳에서 LoginUseCase 를 주입하게된다.
위와같이 단순한 주입이 아닌 Room처럼 복잡한 생성이 필요한 경우에도
@Module
object DatabaseModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext appContext: Context): AppDatabase {
return Room.databaseBuilder(
appContext,
AppDatabase::class.java,
"logging.db"
).build()
}
@Provides
fun provideLogDao(database: AppDatabase): LogDao {
return database.logDao()
}
}
class LoginService @Inject constructor(private val logDao: LogDao) {
}
Module안에서 인스턴스를 생성하고 생성된 인스턴스로 메소드를 호출하여 그 결과를 주입하는것도 가능하다.
ViewModel 주입
💡알파버전에서는 해당 ViewModel 을 inejct 대상이라는것을 알리기위해 @ViewModelInject 을 사용했지만 이제 일정 버전 이상부터 @Inject로 동일하게 가능해지고 대신 @HiltViewModel 를 붙혀야한다.
기존 코드
class HomeFragment : Fragment() {
private val viewModel : HomeViewModel by lazy{
ViewModelProvider(this).get(HomeViewModel::class.java)}
}
class HomeViewModel: ViewModel() {}
Hilt 적용 코드
@AndroidEntryPoint
class HomeFragment : Fragment() {
private val viewModel : HomeViewModel by viewModels()
//val viewModel by viewModels<HomeViewModel>() 둘다 된다.
}
@HiltViewModel
class HomeViewModel @Inject constructor(): ViewModel() {
이곳을 보면 @AndroidEntryPoint 가 있는것을 볼수있는데
Fragment에 해당 어노테이션을 붙히기 위해서는 Fragment를 품고있는 Activity에도 추가해줘야한다.
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {}
예외
M1이슈
에러 내용
Execution failed for task ':app:kaptDebugKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask$KaptExecutionWorkAction
> java.lang.reflect.InvocationTargetException (no error message)
해결
dependencies {
def roomVersion = "2.3.0"
implementation("androidx.room:room-runtime:$roomVersion")
kapt("androidx.room:room-compiler:$roomVersion")
kapt("org.xerial:sqlite-jdbc:3.34.0")
}
viewModels()
Unresolved reference: viewModels
위와같이 viewModels 를 찾지못하는경우
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
'Android' 카테고리의 다른 글
Clean Architecture for Android (0) | 2023.01.14 |
---|---|
Android ViewBinding으로 FindViewByid 랑 작별하기 (0) | 2021.09.25 |
Android Coroutine 사용하기 (0) | 2021.09.25 |
Android Retrofit 사용하여 API 호출하기 (0) | 2021.09.25 |
Android Retrofit 인터셉터 사용하기 (0) | 2021.09.25 |