[코틀린] Dependency injection

Dependency injection

의존관계에 있는 객체를 외부에서 생성해서 인자로 받아서 사용하고자 하는 것.

테스트 용이.

모듈 단위로 객체 관리.

안드로이드 프레임워크 특성상 Activity 등 특정 시점에 자동으로 생성되는 것들이 있다.

이러한 객체들은 생성자 파라미터로 의존 객체를 받을 수 없으므로 이것을 가능하게 해주는 라이브러리들이 Dagger / Hilt.

note :

의존성 주입을 받는 객체는 class 보다 interface로 받는 것이 더 유연하기에 interface 주로 사용.

-Dagger

단점 :

Application 객체 안에 컴포넌트를 만들어야 하는 등 사전에 해야할 작업이 다소 많음.

Jetpack 라이브러리와 같이 사용하려다 보면 코드가 복잡해진다.

-Hilt

Dagger를 쉽게 사용할 수 있도록 개선된 것이 Hilt.

@HiltAndroidApp
class App : Application()

//Application 준비
@Singleton
class SampleRepository @Inject constructor()

//주입할 객체
@AndroidEntryPoint
class MainActivity : AppCompatActivity(){
	@Inject
	lateInit var repository : SampleRepositiry

...
}

//주입 객체를 사용하는 곳. 

1. Adding dependencies

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.38.1'
    }
}

//in root build.gradle file.
plugins {
    kotlin("kapt")
    id("dagger.hilt.android.plugin")
}

android {
    ...
}

dependencies {
    implementation("com.google.dagger:hilt-android:2.38.1")
    kapt("com.google.dagger:hilt-android-compiler:2.38.1")
}

// Allow references to generated code
kapt {
 correctErrorTypes true
}

//in app/build.gradle file.

Steps

Make App.class

@HiltAndroidApp
class ExampleApplication : Application() { ... }
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.choco_tyranno.studyhilt">

    <application
        android:name=".App"
        
        ...
//add name property (android:name=".App") in manifests file.

Inject dependencies into Android classes

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() { ... }
	@Inject lateinit var repository : MyRepository
	
//add (@AndroidEntryPoint) annotation in receive side Android class.
//define supported Object variable by lateinit var with (@Inject) annotation.

Component scopes

Android class Generated component Scope
Application SingletonComponent @Singleton
Activity ActivityRetainedComponent @ActivityRetainedScoped
ViewModel ViewModelComponent @ViewModelScoped
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
View annotated with @WithFragmentBindings ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped
@Singleton
class MyRepository @Inject constructor(){

}

Hilt module

Hilt support object creation management by module.

Hilt module is a class that is annotated with (@Module).

It informs Hilt how to provide instance of certain types(Like Dagger).

Annotate with (@InstallIn) in Hilt modules to tell Hilt which Android class each module will be used or installed in(Unlike Dagger).

@Module
@InstallIn(SingletonComponent::class)
object ApplicationModule {

    @Provides
    fun provideHash() = hashCode().toString()

}

Generated components for Android classes

Hilt component Injector for
SingletonComponent Application
ActivityRetainedComponent N/A
ViewModelComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent View annotated with @WithFragmentBindings
ServiceComponent Service

Inject dependencies into Android classes


@AndroidEntryPoint
class MainFragment : Fragment(R.layout.fragment_main) {
...

    @Inject
    lateinit var applicationHash : String

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Log.d("MainFragment", "appHash: $applicationHash")
...
    }
}

Provide type & Qualifier

@Module
@InstallIn(SingletonComponent::class)
object ApplicationModule {
    @Provides
    fun provideHash() = hashCode().toString()

    @Provides
    fun provideTestString() = "test"

}

//Same type providing occur the below error.
//error: [Dagger/DuplicateBindings] java.lang.String is bound multiple times:

Make Qualifier

@Module
@InstallIn(SingletonComponent::class)
object ApplicationModule {
    @AppHash
    @Provides
    fun provideHash() = hashCode().toString()
    
    @TestString
    @Provides
    fun provideTestString() = "test"
}

//Make qualifier and provide it.
//Write custom annotation (e.g. @AppHash) and (alt+enter) for create annotation AppHash class file.
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class AppHash

//Attach (@Retention, @Qualifier) annotation.
//(@Qualifier) for Hilt.

Inject ViewModel objects with Hilt

@HiltViewModel
class MainViewModel @Inject constructor(
    private val repository: MyRepository
) : ViewModel(){
    fun getRepositoryHash() = repository.hashCode().toString()
}
@Singleton
class MyRepository @Inject constructor()