$30 off During Our Annual Pro Sale. View Details »

Dagger 아닌 Hilt로 Android DI 하기

Dagger 아닌 Hilt로 Android DI 하기

2020년 7월 30일 Android 11 KR 행사에서 발표한 Hilt 발표자료 입니다.
행사: https://developersonair.withgoogle.com/events/a11meetup-korea?talk=meetup3
샘플코드: https://github.com/maryangmin/GDG-Hilt

Jetpack에 드디어 DI 라이브러리가 추가되었습니다. Dagger의 강력한 기능을 그대로 활용하면서 사용이 쉬워진 새로운 DI Solution, Hilt를 소개합니다.

Seungmin 마량

July 30, 2020
Tweet

More Decks by Seungmin 마량

Other Decks in Programming

Transcript

  1. 2019 | Confidential and Proprietary 목차 1. DI Recap 2.

    Dagger Recap 3. A New DI Solution Hilt 4. Hilt 사용하기 5. Hilt 살펴보기 6. 정리
  2. class GithubRepoViewModel( private val repository: GithubRepository ) fun main() {

    GithubRepoViewModel( GithubRepository() ) } With Dependency Injection 의존성 주입 Constructor Parameter
  3. Dagger는 무엇인가? ० Square에서 만든 DI 라이브러리 ० Google에서 Square의

    Dagger를 포크하여 Dagger2 개발 ० Annotation 기반 컴파일 타임 Generated Code로 의존성 주입
  4. Dagger의 장점과 단점 장점 ० 컴파일 타임에 검증한다. 안전하다. ०

    컴파일 타임 Generated Code로 동작하니 런타임 퍼포먼스에 영향을 주지 않는다
  5. Dagger의 장점과 단점 장점 ० 컴파일 타임에 검증한다. 안전하다. ०

    컴파일 타임 Generated Code로 동작하니 런타임 퍼포먼스에 영향을 주지 않는다 단점 ० 학습비용이 높다. 배우기 어렵다 ० 많은 보일러 플레이트 코드가 필요하다
  6. Dagger는 왜 어려울까? ० Android는 Application, Activity 등 프레임워크 클래스

    단위로 의존성 주입 ० Android 프레임워크 클래스의 객체는 OS에서 자체 생성 ० 외부 라이브러리 Dagger가 OS에서 생성되는 프레임워크 클래스 단위로 의존성을 주입하기 위해 많은 지식과 보일러 플레이트 코드 요구
  7. Dagger Boilerplate Code @Singleton @Component(modules = [NetworkModule::class, SubcomponentsModule::class]) interface ApplicationComponent

    { fun inject(activity: LoginActivity) fun loginComponent(): LoginComponent.Factory } @Subcomponent interface LoginComponent { @Subcomponent.Factory interface Factory { fun create(): LoginComponent } fun inject(loginActivity: LoginActivity) fun inject(usernameFragment: LoginUsernameFragment) fun inject(passwordFragment: LoginPasswordFragment) } @Module(subcomponents = LoginComponent::class) class SubcomponentsModule {}
  8. Dagger Boilerplate Code @Singleton @Component(modules = [NetworkModule::class, SubcomponentsModule::class]) interface ApplicationComponent

    { fun inject(activity: LoginActivity) fun loginComponent(): LoginComponent.Factory } @Subcomponent interface LoginComponent { @Subcomponent.Factory interface Factory { fun create(): LoginComponent } fun inject(loginActivity: LoginActivity) fun inject(usernameFragment: LoginUsernameFragment) fun inject(passwordFragment: LoginPasswordFragment) } @Module(subcomponents = LoginComponent::class) class SubcomponentsModule {} Component, Module 정의만 한세월 Subcomponent는 Component와 무엇이 다르지? Factory는 뭐지?
  9. Hilt는 무엇인가요? Dagger 기반 ० Dagger 기반으로 Google이 발전시킨 DI

    라이브러리 ० Dagger의 강력한 기능을 그대로 활용 가능 dependencies { implementation "com.google.dagger:hilt-android:2.28-alpha" kapt "com.google.dagger:hilt-android-compiler:2.28-alpha" }
  10. Hilt는 무엇인가요? Dagger 기반 ० Dagger 기반으로 Google이 발전시킨 DI

    라이브러리 ० Dagger의 강력한 기능을 그대로 활용 가능 Jetpack 라이브러리 ० Jetpack에 포함 ० Activity 등 Android 프레임워크 클래스를 위한 보일러 플레이트 코드 삭제. 의존성을 주입하는 DI 본연의 목적에 집중.
  11. Hilt는 무엇인가요? Dagger 기반 ० Dagger 기반으로 Google이 발전시킨 DI

    라이브러리 ० Dagger의 강력한 기능을 그대로 활용 가능 Jetpack 라이브러리 ० Jetpack에 포함 ० Activity 등 Android 프레임워크 클래스를 위한 보일러 플레이트 코드 삭제. 의존성을 주입하는 DI 본연의 목적에 집중. Dagger의 강력한 기능은 그대로 가지면서 사용하기 쉬운 더 좋은 DI 라이브러리
  12. Hilt 시작하기 ० Hilt Code Generation 시작점 지정하기 (의존성 주입

    시작점 지정하기) ० @HiltAndroidApp @AndroidEntryPoint
  13. Hilt 사용하기 ० Constructor을 호출하여 의존성을 주입받는 포인트 선언 ◦

    @Inject ० 의존성을 생성하는 Constructor 호출 포인트 선언 ◦ @Inject constructor ◦ @Module @Provides @Binds
  14. Hilt 사용하기 ० Constructor을 호출하여 의존성을 주입받는 포인트 선언 ◦

    @Inject ० 의존성을 생성하는 Constructor 호출 포인트 선언 ◦ @Inject constructor ◦ @Module @Provides @Binds
  15. 의존성 주입받기 - @Inject @AndroidEntryPoint class GithubReposActivity : AppCompatActivity() {

    @Inject lateinit var adapter: GithubReposAdapter } 의존성을 주입받으려는 변수에 @Inject
  16. 의존성 주입받기 - @Inject @AndroidEntryPoint class GithubReposActivity : AppCompatActivity() {

    @Inject lateinit var adapter: GithubReposAdapter } 의존성을 주입받으려는 변수에 @Inject 어떤 constructor를 호출하지?
  17. Hilt 사용하기 ० Constructor을 호출하여 의존성을 주입받는 포인트 선언 ◦

    @Inject ० 의존성을 생성하는 Constructor 호출 포인트 선언 ◦ @Inject constructor ◦ @Module @Provides @Binds
  18. 의존성 생성하기 - @Inject constructor @AndroidEntryPoint class GithubReposActivity : AppCompatActivity()

    { @Inject lateinit var adapter: GithubReposAdapter } 의존성을 주입받으려는 변수에 @Inject 어떤 constructor를 호출하지?
  19. 의존성 생성하기 - @Inject constructor @AndroidEntryPoint class GithubReposActivity : AppCompatActivity()

    { @Inject lateinit var adapter: GithubReposAdapter } class GithubReposAdapter : @Inject constructor() 의존성을 생성하는 constructor에 @Inject
  20. Hilt 사용하기 ० Constructor을 호출하여 의존성을 주입받는 포인트 선언 ◦

    @Inject ० 의존성을 생성하는 Constructor 호출 포인트 선언 ◦ @Inject constructor ◦ @Module @Provides @Binds
  21. 의존성 생성하기 - @Module @Provides @Binds @Module @InstallIn(ActivityComponent::class) abstract class

    SchedulerProviderModule { @Binds abstract fun bindSchedulerProvider( schedulerProvider: SchedulerProvider ): SchedulerProviderInterface @Provides fun providesSchedulerProvider(): SchedulerProviderInterface { return SchedulerProvider() } }
  22. 의존성 생성하기 - @Module @Provides @Binds @Module @InstallIn(ActivityComponent::class) abstract class

    SchedulerProviderModule { @Binds abstract fun bindSchedulerProvider( schedulerProvider: SchedulerProvider ): SchedulerProviderInterface @Provides fun providesSchedulerProvider(): SchedulerProviderInterface { return SchedulerProvider() } } @Module @InstallIn constructor 호출하는 모듈 선언
  23. 의존성 생성하기 - @Module @Provides @Binds @Module @InstallIn(ActivityComponent::class) abstract class

    SchedulerProviderModule { @Binds abstract fun bindSchedulerProvider( schedulerProvider: SchedulerProvider ): SchedulerProviderInterface @Provides fun providesSchedulerProvider(): SchedulerProviderInterface { return SchedulerProvider() } } @Binds @Provides constructor 호출 (의존성 생성)
  24. Hilt 사용하기 ० Constructor을 호출하여 의존성을 주입받는 포인트 선언 ◦

    @Inject ० 의존성을 생성하는 Constructor 호출 포인트 선언 ◦ @Inject constructor ◦ @Module @Provides @Binds
  25. Hilt 사용하기 ० Constructor을 호출하여 의존성을 주입받는 포인트 선언 ◦

    @Inject ० 의존성을 생성하는 Constructor 호출 포인트 선언 ◦ @Inject constructor ◦ @Module @Provides @Binds 직관적인 Annotaion만으로 쉽게 DI 가능!
  26. 의존성 생성하기 - @ViewModelInject constructor class GithubReposViewModel @ViewModelInject constructor( private

    val repository: GithubRepository, private val schedulerProvider: SchedulerProviderInterface, @Assisted private val savedStateHandle: SavedStateHandle ) : ViewModel() {
  27. 의존성 생성하기 - @ViewModelInject constructor class GithubReposViewModel @ViewModelInject constructor( private

    val repository: GithubRepository, private val schedulerProvider: SchedulerProviderInterface, @Assisted private val savedStateHandle: SavedStateHandle ) : ViewModel() { 의존성을 생성하는 ViewModel constructor에 @ViewModelInject
  28. 의존성 주입받기 - by viewModels() class GithubReposViewModel @ViewModelInject constructor( private

    val repository: GithubRepository, private val schedulerProvider: SchedulerProviderInterface, @Assisted private val savedStateHandle: SavedStateHandle ) : ViewModel() { @AndroidEntryPoint class GithubReposActivity : BaseViewModelActivity() { val viewModel: GithubReposViewModel by viewModels() }
  29. 의존성 주입받기 - by viewModels() class GithubReposViewModel @ViewModelInject constructor( private

    val repository: GithubRepository, private val schedulerProvider: SchedulerProviderInterface, @Assisted private val savedStateHandle: SavedStateHandle ) : ViewModel() { @AndroidEntryPoint class GithubReposActivity : BaseViewModelActivity() { val viewModel: GithubReposViewModel by viewModels() } 의존성을 주입받으려는 viewModel 변수에 by androidx.activity.viewModels()
  30. @Assisted SavedStateHandle class GithubReposViewModel @ViewModelInject constructor( private val repository: GithubRepository,

    private val schedulerProvider: SchedulerProviderInterface, @Assisted private val savedStateHandle: SavedStateHandle ) : ViewModel() { @AndroidEntryPoint class GithubReposActivity : BaseViewModelActivity() { val viewModel: GithubReposViewModel by viewModels() }
  31. Hilt로 Test하기 ० Unit Test는 지원하지 않음 ◦ Mocking으로 의존성

    생성 가능 ० 통합 Test에서 Hilt로 의존성 생성 가능 ◦ @HiltAndroidTest @HiltAndroidRule ० 테스트 클래스 단위로 Module을 재선언 ◦ @UninstallModules
  32. 통합 Test 의존성 생성 @HiltAndroidTest @RunWith(AndroidJUnit4::class) class SchedulerProviderTest { @get:Rule

    var hiltRule = HiltAndroidRule(this) @Inject lateinit var schedulerProvider: SchedulerProviderInterface @Before fun init() { hiltRule.inject() } }
  33. 통합 Test 의존성 생성 @HiltAndroidTest @RunWith(AndroidJUnit4::class) class SchedulerProviderTest { @get:Rule

    var hiltRule = HiltAndroidRule(this) @Inject lateinit var schedulerProvider: SchedulerProviderInterface @Before fun init() { hiltRule.inject() } } @HiltAndroidTest @HiltAndroidRule 활용하여 inject()
  34. 테스트 클래스 Module 재선언 @HiltAndroidTest @UninstallModules(SchedulerProviderModule::class) @RunWith(AndroidJUnit4::class) class SchedulerProviderTest {

    @Module @InstallIn(ApplicationComponent::class) abstract class TestSchedulerProviderModule { @Binds abstract fun bindSchedulerProvider( schedulerProvider: TestSchedulerProvider ): SchedulerProviderInterface } }
  35. 테스트 클래스 Module 재선언 @HiltAndroidTest @UninstallModules(SchedulerProviderModule::class) @RunWith(AndroidJUnit4::class) class SchedulerProviderTest {

    @Module @InstallIn(ApplicationComponent::class) abstract class TestSchedulerProviderModule { @Binds abstract fun bindSchedulerProvider( schedulerProvider: TestSchedulerProvider ): SchedulerProviderInterface } } @UninstallModules 활용하여 필요한 모듈 재선언
  36. 테스트 클래스 Module 재선언 @HiltAndroidTest @UninstallModules(SchedulerProviderModule::class) @RunWith(AndroidJUnit4::class) class SchedulerProviderTest {

    @Module @InstallIn(ApplicationComponent::class) abstract class TestSchedulerProviderModule { @Binds abstract fun bindSchedulerProvider( schedulerProvider: TestSchedulerProvider ): SchedulerProviderInterface } } @UninstallModules 활용하여 필요한 모듈 재선언 프로덕션 코드에 영향주지 않고 개별 테스트 DI 가능!
  37. Component ० 각 Android 프레임워크 클래스에서 Component를 들고있다 ◦ ApplicationComponent,

    ActivityComponent, FragmentComponent... ० Component가 Module로 의존성을 생성하고, @Inject로 요청한 변수에 의존성을 주입한다 @Module @InstallIn(ActivityComponent::class)
  38. Component ० 각 Android 프레임워크 클래스에서 Component를 들고있다 ◦ ApplicationComponent,

    ActivityComponent, FragmentComponent... ० Component가 Module로 의존성을 생성하고, @Inject로 요청한 변수에 의존성을 주입한다 Application, Activity 등 프레임워크 클래스에서 Component를 이용해 DI를 수행한다
  39. Component Generated Code @Component( modules = { ApplicationContextModule.class, GithubRepositoryModule.class, SchedulerProviderModule.class,

    ... } ) interface ApplicationComponent { fun inject(application: Application) } Component가 Module을 들고있다
  40. Component Generated Code @Component( modules = { ApplicationContextModule.class, GithubRepositoryModule.class, SchedulerProviderModule.class,

    ... } ) interface ApplicationComponent { fun inject(application: Application) } Application에서 inject(this)을 호출하면 들고있는 Component로 DI를 수행한다 Component가 Module을 들고있다
  41. Component & Scope Annotation ० 각 Android 프레임워크 클래스에서 Component를

    들고있다 ◦ ApplicationComponent, ActivityComponent, FragmentComponent... ० Component가 Module로 의존성을 생성하고, @Inject로 요청한 변수에 의존성을 주입한다 ० Component에 맞는 Scope Annotation 매칭
  42. Component & Scope Annotation @Module @InstallIn(ApplicationComponent::class) class GithubRepositoryModule { @Singleton

    @Provides fun bindGithubRepository(): GithubRepository { return GithubRepository() } }
  43. Component & Scope Annotation @Module @InstallIn(ApplicationComponent::class) class GithubRepositoryModule { @Singleton

    @Provides fun bindGithubRepository(): GithubRepository { return GithubRepository() } } ApplicationComponent - Singleton 매칭
  44. Component & Scope Annotation @Module @InstallIn(ActivityComponent::class) abstract class SchedulerProviderModule {

    @ActivityScoped @Binds abstract fun bindSchedulerProvider( schedulerProvider: SchedulerProvider ): SchedulerProviderInterface }
  45. Component & Scope Annotation @Module @InstallIn(ActivityComponent::class) abstract class SchedulerProviderModule {

    @ActivityScoped @Binds abstract fun bindSchedulerProvider( schedulerProvider: SchedulerProvider ): SchedulerProviderInterface } ActivityComponent - ActivityScoped 매칭
  46. Component & Scope Annotation @Module @InstallIn(ActivityComponent::class) abstract class SchedulerProviderModule {

    @ActivityScoped @Binds abstract fun bindSchedulerProvider( schedulerProvider: SchedulerProvider ): SchedulerProviderInterface } ActivityComponent - ActivityScoped 매칭 Scope Annotation 없으면 매번 새로 생성!
  47. 의존성 그래프 네비게이션 지원 ० Android Studio 4.1부터 gutter로 Dagger

    Hilt 의존성 그래프 네비게이션 지원 출처: https://medium.com/androiddevelopers/dagger-navigation-support-in-android-studio-49aa5d149ec9
  48. Dagger의 장점과 단점 장점 ० 컴파일 타임에 검증한다. 안전하다. ०

    컴파일 타임 Generated Code로 동작하니 런타임 퍼포먼스에 영향을 주지 않는다 단점 ० 학습비용이 높다. 배우기 어렵다 ० 많은 보일러 플레이트 코드가 필요하다
  49. Hilt는 무엇인가요? Dagger 기반 ० Dagger 기반으로 Google이 발전시킨 DI

    라이브러리 ० Dagger의 강력한 기능을 그대로 활용 가능 Jetpack 라이브러리 ० Jetpack에 포함 ० Activity 등 Android 프레임워크 클래스를 위한 보일러 플레이트 코드 삭제. 의존성을 주입하는 DI 본연의 목적에 집중. Dagger의 강력한 기능은 그대로 가지면서 사용하기 쉬운 더 좋은 DI 라이브러리
  50. Hilt 사용하기 ० Constructor을 호출하여 의존성을 주입받는 포인트 선언 ◦

    @Inject ० 의존성을 생성하는 Constructor 호출 포인트 선언 ◦ @Inject constructor ◦ @Module @Provides @Binds 직관적인 Annotaion만으로 쉽게 DI 가능!
  51. Component ० 각 Android 프레임워크 클래스에서 Component를 들고있다 ◦ ApplicationComponent,

    ActivityComponent, FragmentComponent... ० Component가 Module로 의존성을 생성하고, @Inject로 요청한 변수에 의존성을 주입한다 Application, Activity 등 프레임워크 클래스에서 Component를 이용해 DI를 수행한다
  52. 쉽고 강력한 Hilt로 앱을 더 안정적으로 만드세요 서비스는 커지고 복잡해집니다

    코드를 관심사 단위로 분리해야 합니다 의존성을 잘 연결해야 합니다 DI는 필수입니다