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를 소개합니다.

1763273018a6f71ffe4162dd57b60a56?s=128

Seungmin - maryang

July 30, 2020
Tweet

Transcript

  1. Dagger 아닌 Hilt로 Android DI 하기 이승민 Google Developers Expert

    Banksalad Frontend Engineering Manager
  2. 2019 | Confidential and Proprietary 목차 1. DI Recap 2.

    Dagger Recap 3. A New DI Solution Hilt 4. Hilt 사용하기 5. Hilt 살펴보기 6. 정리
  3. DI Recap

  4. ० Dependency Injection (의존성 주입) ० 의존성 객체를 외부로부터 주입받는다

    DI는 무엇인가? A Class B Class new
  5. No Dependency Injection class GithubRepoViewModel( private val repository: GithubRepository =

    GithubRepository() )
  6. class GithubRepoViewModel( private val repository: GithubRepository = GithubRepository() ) No

    Dependency Injection 의존성 생성
  7. With Dependency Injection class GithubRepoViewModel( private val repository: GithubRepository )

    fun main() { GithubRepoViewModel( GithubRepository() ) }
  8. class GithubRepoViewModel( private val repository: GithubRepository ) fun main() {

    GithubRepoViewModel( GithubRepository() ) } With Dependency Injection 의존성 주입 Constructor Parameter
  9. Dagger Recap

  10. Dagger는 무엇인가? ० Square에서 만든 DI 라이브러리 ० Google에서 Square의

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

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

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

    단위로 의존성 주입 ० Android 프레임워크 클래스의 객체는 OS에서 자체 생성 ० 외부 라이브러리 Dagger가 OS에서 생성되는 프레임워크 클래스 단위로 의존성을 주입하기 위해 많은 지식과 보일러 플레이트 코드 요구
  14. 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 {}
  15. 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는 뭐지?
  16. Dagger의 강력한 기능은 그대로 가지면서 사용하기 쉬운 더 좋은 DI

    라이브러리는 없을까?
  17. A New DI Solution Hilt

  18. 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" }
  19. Hilt는 무엇인가요? Dagger 기반 ० Dagger 기반으로 Google이 발전시킨 DI

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

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

  22. Hilt 시작하기 ० Hilt Code Generation 시작점 지정하기 (의존성 주입

    시작점 지정하기) ० @HiltAndroidApp @AndroidEntryPoint
  23. @HiltAndroidApp @AndroidEntryPoint @HiltAndroidApp class BaseApplication : Application() @AndroidEntryPoint class GithubReposActivity

    : AppCompatActivity() {
  24. Hilt 사용하기 ० Constructor을 호출하여 의존성을 주입받는 포인트 선언 ◦

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

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

    @Inject lateinit var adapter: GithubReposAdapter }
  27. 의존성 주입받기 - @Inject @AndroidEntryPoint class GithubReposActivity : AppCompatActivity() {

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

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

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

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

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

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

    SchedulerProviderModule { @Binds abstract fun bindSchedulerProvider( schedulerProvider: SchedulerProvider ): SchedulerProviderInterface @Provides fun providesSchedulerProvider(): SchedulerProviderInterface { return SchedulerProvider() } }
  34. 의존성 생성하기 - @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 호출하는 모듈 선언
  35. 의존성 생성하기 - @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 호출 (의존성 생성)
  36. Hilt 사용하기 ० Constructor을 호출하여 의존성을 주입받는 포인트 선언 ◦

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

    @Inject ० 의존성을 생성하는 Constructor 호출 포인트 선언 ◦ @Inject constructor ◦ @Module @Provides @Binds 직관적인 Annotaion만으로 쉽게 DI 가능!
  38. Androidx Jetpack Support ० AAC ViewModel 주입 Support

  39. 의존성 생성하기 - @ViewModelInject constructor class GithubReposViewModel @ViewModelInject constructor( private

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

    val repository: GithubRepository, private val schedulerProvider: SchedulerProviderInterface, @Assisted private val savedStateHandle: SavedStateHandle ) : ViewModel() { 의존성을 생성하는 ViewModel constructor에 @ViewModelInject
  41. 의존성 주입받기 - 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() }
  42. 의존성 주입받기 - 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()
  43. @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() }
  44. Hilt로 Test하기 ० Unit Test는 지원하지 않음 ◦ Mocking으로 의존성

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

    var hiltRule = HiltAndroidRule(this) @Inject lateinit var schedulerProvider: SchedulerProviderInterface @Before fun init() { hiltRule.inject() } }
  46. 통합 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()
  47. 테스트 클래스 Module 재선언 @HiltAndroidTest @UninstallModules(SchedulerProviderModule::class) @RunWith(AndroidJUnit4::class) class SchedulerProviderTest {

    @Module @InstallIn(ApplicationComponent::class) abstract class TestSchedulerProviderModule { @Binds abstract fun bindSchedulerProvider( schedulerProvider: TestSchedulerProvider ): SchedulerProviderInterface } }
  48. 테스트 클래스 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 활용하여 필요한 모듈 재선언
  49. 테스트 클래스 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 가능!
  50. Hilt 살펴보기

  51. Component @Module @InstallIn(ActivityComponent::class)

  52. Component ० 각 Android 프레임워크 클래스에서 Component를 들고있다 ◦ ApplicationComponent,

    ActivityComponent, FragmentComponent...
  53. Component ० 각 Android 프레임워크 클래스에서 Component를 들고있다 ◦ ApplicationComponent,

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

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

    ... } ) interface ApplicationComponent { fun inject(application: Application) }
  56. Component Generated Code @Component( modules = { ApplicationContextModule.class, GithubRepositoryModule.class, SchedulerProviderModule.class,

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

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

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

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

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

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

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

    @ActivityScoped @Binds abstract fun bindSchedulerProvider( schedulerProvider: SchedulerProvider ): SchedulerProviderInterface } ActivityComponent - ActivityScoped 매칭 Scope Annotation 없으면 매번 새로 생성!
  64. Component, Scope 작성할때는 알겠는데 각 화면에서 의존성이 어디에서 왔는지 알기

    어려워요!
  65. 의존성 그래프 네비게이션 지원 ० Android Studio 4.1부터 gutter로 Dagger

    Hilt 의존성 그래프 네비게이션 지원 출처: https://medium.com/androiddevelopers/dagger-navigation-support-in-android-studio-49aa5d149ec9
  66. Migrating Dagger to Hilt ० https://dagger.dev/hilt/migration-guide.html

  67. Migrating Dagger to Hilt ० https://dagger.dev/hilt/migration-guide.html ◦ Component to @EntryPoint

    ◦ Annotate @InstallIn to Modules
  68. 정리

  69. Dagger의 장점과 단점 장점 ० 컴파일 타임에 검증한다. 안전하다. ०

    컴파일 타임 Generated Code로 동작하니 런타임 퍼포먼스에 영향을 주지 않는다 단점 ० 학습비용이 높다. 배우기 어렵다 ० 많은 보일러 플레이트 코드가 필요하다
  70. Dagger의 강력한 기능은 그대로 가지면서 사용하기 쉬운 더 좋은 DI

    라이브러리는 없을까?
  71. Hilt는 무엇인가요? Dagger 기반 ० Dagger 기반으로 Google이 발전시킨 DI

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

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

    ActivityComponent, FragmentComponent... ० Component가 Module로 의존성을 생성하고, @Inject로 요청한 변수에 의존성을 주입한다 Application, Activity 등 프레임워크 클래스에서 Component를 이용해 DI를 수행한다
  74. Reference ० https://developer.android.com/training/dependency-injection/hilt-android ० https://developer.android.com/training/dependency-injection/hilt-jetpack#kotlin ० https://developer.android.com/training/dependency-injection/hilt-testing ० https://medium.com/androiddevelopers/dependency-injection-on-android-with-hilt-67b6031e62d ०

    https://youtu.be/B56oV3IHMxg ० https://github.com/maryangmin/GDG-Hilt
  75. 서비스는 커지고 복잡해집니다 코드를 관심사 단위로 분리해야 합니다 의존성을 잘

    연결해야 합니다 DI는 필수입니다
  76. 쉽고 강력한 Hilt로 앱을 더 안정적으로 만드세요 서비스는 커지고 복잡해집니다

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