-
test
개요
기존의 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