DI 란?
DI = Dependency Injection = 의존성(종속성) 주입
• 한 클래스가 다른 클래스에 대한 참조가 필요할 때
• Ex) Car 클래스에서 Engine 클래스에 대한 참조가 필수 일때, 의존성을 가지게 된다
class Engine() {}
class Car {
private val engine = Engine() // 의존성
}
DI 란?
클래스가 필요한 의존성(인스턴스)을 얻는 세 가지 방법
1. 필요한 클래스 인스턴스 직접 생성 및 초기화
2. 다른 곳으로 부터 끌어오기 (Ex. Context 게터, getSystemService() 등)
3. 파라미터로 제공 받기 (생성자나 함수 등 에서 필요한 의존성을 전달 받기)
DI 란?
DI(의존성 주입) 을 하지 않는 코드
class Car {
private val engine = Engine()
fun start() {
engine.start()
}
}
fun main(args: Array) {
val car = Car()
car.start()
}
• Car와 Engine은 강하게 커플링 됨
• 테스트가 어려움 (*Test Double 등)
* Test Double : 테스트를 진행하기 어려운 경우 이를 대신해 테스트를 진행할 수 있도록 만들어주는 객체를 말한다.
DI 란?
DI(의존성 주입) 을 사용한 코드
class Car(private val engine: Engine) {
fun start() {
engine.start()
}
}
fun main(args: Array) {
val engine = Engine()
val car = Car(engine)
car.start()
}
• 재사용성 ⬆ (Car 클래스)
• Ex) ElectricEngine
• 테스트 쉬움
• Ex) FakeEngine
DI 란?
Android 에서 주로 사용하는 2가지 DI 방법
1. 생성자 주입(Constructor Injection)
2. 필드 주입(Field Injection)(or Setter Injection)
class Car {
lateinit var engine: Engine
fun start() {
engine.start()
}
}
fun main(args: Array) {
val car = Car()
car.engine = Engine() // 필드 주입
car.start()
}
Automated dependency injection
수동 의존성 주입의 문제점
• 큰 프로젝트에서 올바른 DI 를 해주기 위해서는 많은 양의 boilerplate code 필요
• 앱의 수명 주기를 관리하는 지정 컨테이너를 직접 작성해야하고 유지해야함
Automated dependency injection
의존성 주입 라이브러리
• 의존성 생성과 제공 프로세스를 자동화 하는 라이브러리
• 런타임에 의존성을 주입하는 리플렉션 기반 솔루션
• 컴파일 타임에 의존성을 연결하는 코드를 생성하는 정적 솔루션
• Google 의 Dagger
• Java, Kotlin 및 Android 용 DI로 널리 사용
• 완전 정적 컴파일 타임 DI로 리플렉션 기반 솔루션(Guice 등)의 성능 문제를 해결
Use Hilt in your Android app
Android 의존성 주입을 위한 Jetpack의 권장 라이브러리
• Android 클래스 (Activity, Fragment 등) 에 표준 컨테이너를 제공
• 자동으로 수명주기를 관리하여 DI 를 적용
• 2021년 4월 부터 stable 버전 릴리즈 (현재 2.38.1ver)
Use Hilt in your Android app
Dagger의 bene
fi
t을 그대로 가지면서 +
𝜶
• compile-time correctness
• runtime performance
• scalability
• Android Studio support that Dagger provides
• + Boilerplate Code 감소 (개발자가 작성해야 하는 코드 감소)
• + Jetpack Library 지원 (별도 컨테이너 구현 필요 X)
• + Dagger2 의 러닝커브에 비해 쉬운 사용성
Dagger-Hilt
지원하는 Android Class + Jetpack Libraries
• Application (by using @HiltAndroidApp)
• Activity
• Fragment
• View
• Service
• BroadcastReceiver
ViewModel (by using @HiltViewModel)
Navigation
Compose
WorkManager
Dagger-Hilt 적용 해보기
Application
class MyApplication : Application(), HasAndroidInjector {
@Inject
lateinit var androidInjector: DispatchingAndroidInjector<Any>
override fun androidInjector(): AndroidInjector<Any> {
return androidInjector
}
override fun onCreate() {
super.onCreate()
DaggerAppComponent.factory()
.create(this)
.inject(this)
}
}
Dagger
@HiltAndroidApp
class MyApplication : Application() { /* … */ }
Hilt
Dagger-Hilt 적용 해보기
Activity, Fragment (Module 사용)
class MyActivity : AppCompatActivity(), HasAndroidInjector {
@field:Inject
lateinit var androidInjector: DispatchingAndroidInjector<Any>
protected set
override fun androidInjector(): AndroidInjector<Any> {
return androidInjector
}
Dagger
@Singleton
@Component(
modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
ActivityModule::class
]
)
abstract class AppComponent : AndroidInjector<MyApplication> {
@Component.Factory
abstract class Factory : AndroidInjector.Factory<MyApplication>
}
@Module
interface ActivityModule {
@ActivityScope @ContributesAndroidInjector(modules = [MyModule::class])
fun myActivity(): MyActivity
@Module
abstract class MyModule {
@Binds
abstract fun bindFragmentActivity(activity: MyActivity): FragmentActivity
컴포넌트 정의
(안드로이드 지원 모듈, Activity, App 모듈)
Activity Scope 를 가지는 모듈
해당 Activity 모듈에 bind 및 provide 할 종속성 정의
HasAndroidInjector 를 반드시 구현, 및 @Inject 로 주입 받기
Dagger-Hilt 적용 해보기
Activity, Fragment (Module 사용)
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
@Inject
lateinit var analytics: AnalyticsService
...
}
// If AnalyticsService is an interface.
@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {
@Singleton
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
}
@Inject 로 주입 받기
Hilt
Module 이 Bind 할 Hilt 컴포넌트 지정
@Bind 로 주입
// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {
@Singleton
@Provides
fun provideAnalyticsService(): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(AnalyticsService::class.java)
}
}
@Provides 로 의존성 제공
object
Fragment 도 동일
Dagger-Hilt 적용 해보기
Jetpack ViewModel
public class DaggerAwareViewModelFactory
@Inject public constructor(
private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards
Provider<ViewModel>>
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
var creator = creators[modelClass]
if (creator == null) {
for ((key, value) in creators) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
if (creator == null) {
error("unknown model class $modelClass")
}
try {
return creator.get() as T
} catch (e: Exception) {
throw e
}
}
}
@MapKey
public annotation class ViewModelKey(val value: KClass<out ViewModel>)
Dagger
abstract class MyModule {
@Binds
fun provideViewModelFactory(factory: DaggerAwareViewModelFactory): ViewModelProvider.Factory
@Binds
@IntoMap
@ViewModelKey(MyViewModel::class)
abstract fun bindMyViewModel(viewModel: MyViewModel): ViewModel
@Binds
abstract fun bindFragmentActivity(activity: MyActivity): FragmentActivity
…
@Module
companion object {
@JvmStatic
@ViewModelInject
@Provides
fun provideMyViewModel(
activity: MyActivity,
viewModelFactory: ViewModelProvider.Factory
): MyViewModel = ViewModelProvider(activity, viewModelFactory).get()
Map Key 를 사용하는 ViewModelFactory 정의
ViewModelFactory 를 통해 ViewModel 주입
@field:ViewModelInject
@field:Inject
public lateinit var viewModel: VM
protected set
Map 을 사용한 멀티 바인딩
Activity or Fragment 에서 Inject 하여 사용
여전히 매번 모듈 작성에 대한 Boilerplate Code 발생
Dagger-Hilt 적용 해보기
Jetpack ViewModel
@AndroidEntryPoint
class MyActivity : AppCompatActivity() {
val viewModel: MyViewModel by viewModels()
@HiltViewModel
class MyViewModel @Inject constructor(
MyRepository: MyRepository
) : ViewModel()
Hilt
by using @HiltViewModel
by viewModels() KTX 확장을 사용하여 주입 가능
모듈 작성 시 발생하는 Boilerplate Code 없음
Jetpack 라이브러리를 지원, 직관적인 사용 가능
Migrating to Hilt
스마일페이 -> G마켓, 옥션 ?
• Dagger and Hilt code can coexist in the same codebase.
• However, in most cases it is best to use Hilt to manage all of your usage of
Dagger on Android.