Ce diaporama a bien été signalé.
Le téléchargement de votre SlideShare est en cours. ×

Using hilt in a modularized project

Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Chargement dans…3
×

Consultez-les par la suite

1 sur 91 Publicité

Using hilt in a modularized project

Télécharger pour lire hors ligne

Modularizing a project is never easy, a lot of files to move and the dependencies between them is not always what we expect. Then the Dagger configuration used in a single module project often doesn't scale well to a multi module project. Hilt is opinionated about the configuration to use (we don't need to argue anymore about using component dependencies or subcomponents!) and this configuration works perfectly even in a multi module project. In this talk we'll see first an introduction to Hilt and a comparison with Dagger to understand why it's easier to configure. Then we'll see how to leverage it in a multi module project (both in a standard layered architecture and in a Clean Architecture that uses the Dependency Inversion) to improve build speed and code testability. Spoiler alert: using sample apps that include a single feature in the app helps a lot!

Modularizing a project is never easy, a lot of files to move and the dependencies between them is not always what we expect. Then the Dagger configuration used in a single module project often doesn't scale well to a multi module project. Hilt is opinionated about the configuration to use (we don't need to argue anymore about using component dependencies or subcomponents!) and this configuration works perfectly even in a multi module project. In this talk we'll see first an introduction to Hilt and a comparison with Dagger to understand why it's easier to configure. Then we'll see how to leverage it in a multi module project (both in a standard layered architecture and in a Clean Architecture that uses the Dependency Inversion) to improve build speed and code testability. Spoiler alert: using sample apps that include a single feature in the app helps a lot!

Publicité
Publicité

Plus De Contenu Connexe

Diaporamas pour vous (20)

Similaire à Using hilt in a modularized project (20)

Publicité

Plus par Fabio Collini (20)

Plus récents (20)

Publicité

Using hilt in a modularized project

  1. 1. Using Hilt in a modularized project Fabio Collini @fabioCollini
  2. 2. Dependency Injection Vs Service Locator
  3. 3. class MyClass { private val collaborator2 = Collaborator2() fun execute() { val value = Collaborator1.loadSomething() collaborator2.doSomethingElse(value) } } Plaincode
  4. 4. class MyClass(serviceLocator: ServiceLocator) { private val collaborator1 = serviceLocator.collaborator1 private val collaborator2 = serviceLocator.collaborator2 fun execute() { val value = collaborator1.loadSomething() collaborator2.doSomethingElse(value) } } ServiceLocator
  5. 5. class MyClass( private val collaborator1: Collaborator1, private val collaborator2: Collaborator2 ) { fun execute() { val value = collaborator1.loadSomething() collaborator2.doSomethingElse(value) } } DependencyInjection
  6. 6. class MyClass( private val collaborator1: Collaborator1, private val collaborator2: Collaborator2 ) { fun execute() { val value = collaborator1.loadSomething() collaborator2.doSomethingElse(value) } } class MyClass(serviceLocator: ServiceLocator) { private val collaborator1 = serviceLocator.collaborator1 private val collaborator2 = serviceLocator.collaborator2 fun execute() { val value = collaborator1.loadSomething() collaborator2.doSomethingElse(value) } } ServiceLocatorDependencyInjection Dependencies are retrieved using the Service Locator Dependencies are injected by the container
  7. 7. Dagger is a Dependency Injection framework But a Dagger component can be used as a Service Locator
  8. 8. Is Dagger a Dependency Injection framework? What about Hilt?
  9. 9. WhatIcareabout Dependency Injection on classes we can instantiate Easy setup on classes instantiated by the framework Testability
  10. 10. Objects definition
  11. 11. Dagger&Hilt The same syntax can be used to define objects using both Dagger and Hilt
  12. 12. class MyRepository( private val api: Api ) { fun loadData() = api.load() }
  13. 13. class MyRepository @Inject constructor( private val api: Api ) { fun loadData() = api.load() }
  14. 14. @Singleton class MyRepository @Inject constructor( private val api: Api ) { fun loadData() = api.load() }
  15. 15. Retrofit.Builder() .baseUrl("""...") !//!!... .build() .create(Api"::class.java)
  16. 16. @Provides @Singleton fun provideApi(): Api { return Retrofit.Builder() .baseUrl("""...") !//!!... .build() .create(Api"::class.java) }
  17. 17. @Module object MyModule { @Provides @Singleton fun provideApi(): Api { return Retrofit.Builder() .baseUrl("""...") !//!!... .build() .create(Api"::class.java) } } Using an object instead of a class Dagger generates less code
  18. 18. @Singleton class MyRepository @Inject constructor( private val api: Api ) { fun loadData() = api.load() }
  19. 19. @Singleton class MyRepository @Inject constructor( private val cache: Cache, private val api: Api ) { fun loadData() = cache.load() "?: api.load() }
  20. 20. interface MyRepository { fun loadData(): Any } @Singleton class MyRepositoryImpl @Inject constructor( private val cache: Cache, private val api: Api ) : MyRepository { override fun loadData() = cache.load() "?: api.load() }1
  21. 21. interface MyRepository { fun loadData(): Any } @Singleton class MyRepositoryImpl @Inject constructor( private val cache: Cache, private val api: Api ) : MyRepository { override fun loadData() = cache.load() "?: api.load() }1 @Module interface AnotherModule { @Binds fun MyRepositoryImpl.bindsRepository(): MyRepository }
  22. 22. Hilt
  23. 23. @InstallIn @AndroidEntryPoint @ViewModelInject @HiltAndroidApp @EntryPoint Newannotations
  24. 24. @Module object MyModule { !//!!... }
  25. 25. @Module @InstallIn(SingletonComponent"::class) object MyModule { !//!!... }
  26. 26. @Module @InstallIn(SingletonComponent"::class) object OkHttpConfigModule { @Provides @ElementsIntoSet fun provideDefaultInterceptors(): Set<Interceptor> = emptySet() @Singleton @Provides fun providesOkHttpClient( interceptors: @JvmSuppressWildcards Set<Interceptor> ): OkHttpClient { val httpClient = OkHttpClient.Builder() interceptors.forEach { httpClient.addInterceptor(it) } return httpClient.build() }1 }2 src/main
  27. 27. @Module @InstallIn(SingletonComponent"::class) object OkHttpConfigModule { @Provides @ElementsIntoSet fun provideDefaultInterceptors(): Set<Interceptor> = emptySet() @Singleton @Provides fun providesOkHttpClient( interceptors: @JvmSuppressWildcards Set<Interceptor> ): OkHttpClient { val httpClient = OkHttpClient.Builder() interceptors.forEach { httpClient.addInterceptor(it) } return httpClient.build() }1 }2 @Module @InstallIn(SingletonComponent"::class) object DebugOkHttpConfigModule { @Provides @IntoSet fun provideDebugInterceptor(): Interceptor = HttpLoggingInterceptor() } src/mainsrc/debug
  28. 28. class MainActivity : AppCompatActivity() { @Inject lateinit var permissionManager: PermissionManager @Inject lateinit var mainNavigator: MainNavigator !//!!... }
  29. 29. @AndroidEntryPoint class MainActivity : AppCompatActivity() { @Inject lateinit var permissionManager: PermissionManager @Inject lateinit var mainNavigator: MainNavigator !//!!... }
  30. 30. @AndroidEntryPoint class MainActivity : AppCompatActivity() { @Inject lateinit var permissionManager: PermissionManager @Inject lateinit var mainNavigator: MainNavigator !//!!... }
  31. 31. @AndroidEntryPoint class MainActivity : AppCompatActivity() { private val viewModel: MyViewModel by viewModels() @Inject lateinit var permissionManager: PermissionManager @Inject lateinit var mainNavigator: MainNavigator !//!!... }
  32. 32. class MyViewModel @ViewModelInject constructor( private val useCase: MyUseCase ) : ViewModel() { !//!!... }
  33. 33. class MyViewModel @ViewModelInject constructor( private val useCase: MyUseCase, @Assisted private val handle: SavedStateHandle ) : ViewModel() { !//!!... }
  34. 34. class MyViewModel @ViewModelInject constructor( private val useCase: MyUseCase, @Assisted private val handle: SavedStateHandle ) : ViewModel() { init { load(handle.get<String>("Id")) } !//!!... }
  35. 35. class MyApp : Application() { @Inject lateinit var dependency: Dependency !//!!... }
  36. 36. @HiltAndroidApp class MyApp : Application() { @Inject lateinit var dependency: Dependency !//!!... }
  37. 37. No more components! (sort of…)
  38. 38. @Component( modules = { "//""... } ) @Singleton public abstract static class SingletonC implements SingletonComponent, "//… { } @Subcomponent( modules = { "//""... } ) @ActivityScoped public abstract static class ActivityC implements ActivityComponent, "//""... { @Subcomponent.Builder abstract interface Builder extends ActivityComponentBuilder { } }
  39. 39. !!/** * A generated base class to be extended by the @dagger.hilt.android.AndroidEntryPoint annotated class. * If using the Gradle plugin, this is swapped as the base class via bytecode transformation. !*/ public abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManagerHolder { "//""... @Override protected void onCreate(@Nullable Bundle savedInstanceState) { ((MainActivity_GeneratedInjector) this.generatedComponent()) .injectMainActivity(UnsafeCasts.<MainActivity>unsafeCast(this)); super.onCreate(savedInstanceState); } "//""... }
  40. 40. !!/** * A generated base class to be extended by the @dagger.hilt.android.AndroidEntryPoint annotated class. * If using the Gradle plugin, this is swapped as the base class via bytecode transformation. !*/ public abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManagerHolder { "//""... @Override protected void onCreate(@Nullable Bundle savedInstanceState) { ((MainActivity_GeneratedInjector) this.generatedComponent()) .injectMainActivity(UnsafeCasts.<MainActivity>unsafeCast(this)); super.onCreate(savedInstanceState); } "//""... }
  41. 41. !!/** * A generated base class to be extended by the @dagger.hilt.android.AndroidEntryPoint annotated class. * If using the Gradle plugin, this is swapped as the base class via bytecode transformation. !*/ public abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManagerHolder { "//""... @Override protected void onCreate(@Nullable Bundle savedInstanceState) { ((MainActivity_GeneratedInjector) this.generatedComponent()) .injectMainActivity(UnsafeCasts.<MainActivity>unsafeCast(this)); super.onCreate(savedInstanceState); } "//""... }
  42. 42. MonolithicComponent Simple sometimes you need an extra Qualifier annotation Less generated code Everything can be injected everywhere internal can be useful to limit the scope
  43. 43. @EntryPoint @InstallIn(SingletonComponent"::class) interface MyEntryPoint { val myUseCase: MyUseCase fun inject(something: Something) }
  44. 44. @EntryPoint @InstallIn(SingletonComponent"::class) interface MyEntryPoint { val myUseCase: MyUseCase fun inject(something: Something) } val entryPoint = EntryPointAccessors.fromApplication( app, MyEntryPoint"::class.java) val useCase = entryPoint.myUseCase
  45. 45. Testing
  46. 46. class MyClass @Inject constructor( private val collaborator1: Collaborator1, private val collaborator2: Collaborator2 ) { fun execute() { val value = collaborator1.loadSomething() collaborator2.doSomethingElse(value) } }
  47. 47. class MyClassTest { private val collaborator1 = mock<Collaborator1>() private val collaborator2 = mock<Collaborator2>() private val myObject = MyClass(collaborator1, collaborator2) @Test fun testSomething() { whenever(collaborator1.loadSomething()) doReturn "something" myObject.execute() verify(collaborator2).doSomethingElse("something") } }
  48. 48. @Singleton open class MyAnalytics @Inject constructor() interface MyUseCase @Singleton class MyUseCaseImpl @Inject constructor() : MyUseCase @Module @InstallIn(SingletonComponent"::class) interface MyModule { @Binds fun MyUseCaseImpl.bindsUseCase(): MyUseCase } @AndroidEntryPoint class MyActivity : AppCompatActivity() { @Inject lateinit var analytics: MyAnalytics @Inject lateinit var useCase: MyUseCase !//!!... }
  49. 49. @HiltAndroidTest class MyActivityTest { @get:Rule1 val hiltRule = HiltAndroidRule(this) @get:Rule val rule = ActivityTestRule(MyActivity"::class.java, false, false) }
  50. 50. @HiltAndroidTest class MyActivityTest { @get:Rule1 val hiltRule = HiltAndroidRule(this) @get:Rule val rule = ActivityTestRule(MyActivity"::class.java, false, false) @Inject lateinit var analytics: MyAnalytics @Inject lateinit var useCase: MyUseCase @Test fun startActivity() { rule.launchActivity(null) hiltRule.inject() !//now the properties contain the production objects } }
  51. 51. @HiltAndroidTest class MyActivityTest { @get:Rule1 val hiltRule = HiltAndroidRule(this) @get:Rule val rule = ActivityTestRule(MyActivity"::class.java, false, false) @Test fun startActivity() { rule.launchActivity(null) !//!!... } }
  52. 52. @Module @InstallIn(SingletonComponent"::class) object FakeAnalyticsModule { @Provides fun provideAnalytics(): MyAnalytics = mock() } @HiltAndroidTest class MyActivityTest { @get:Rule1 val hiltRule = HiltAndroidRule(this) @get:Rule val rule = ActivityTestRule(MyActivity"::class.java, false, false) @Test fun startActivity() { rule.launchActivity(null) !//!!... } }
  53. 53. @HiltAndroidTest class MyActivityTest { @get:Rule1 val hiltRule = HiltAndroidRule(this) @get:Rule val rule = ActivityTestRule(MyActivity"::class.java, false, false) @Module @InstallIn(SingletonComponent"::class) object FakeAnalyticsModule { @Provides fun provideAnalytics(): MyAnalytics = mock() } @Test fun startActivity() { rule.launchActivity(null) !//!!... } }
  54. 54. @HiltAndroidTest class MyActivityTest { @get:Rule val hiltRule = HiltAndroidRule(this) @get:Rule val rule = ActivityTestRule(MyActivity"::class.java, false, false) @BindValue @JvmField val analytics: MyAnalytics = mock() @Test fun startActivity() { rule.launchActivity(null) !//!!... } }
  55. 55. @HiltAndroidTest class MyActivityTest { @get:Rule val hiltRule = HiltAndroidRule(this) @get:Rule val rule = ActivityTestRule(MyActivity"::class.java, false, false) @BindValue @JvmField val useCase: MyUseCase = mock() @Test fun startActivity() { rule.launchActivity(null) !//!!... } } error: [Dagger/DuplicateBindings] MyUseCase is bound multiple times
  56. 56. @HiltAndroidTest @UninstallModules(MyModule"::class) class MyActivityTest { @get:Rule val hiltRule = HiltAndroidRule(this) @get:Rule val rule = ActivityTestRule(MyActivity"::class.java, false, false) @BindValue @JvmField val useCase: MyUseCase = mock() @Test fun startActivity() { rule.launchActivity(null) !//!!... } }
  57. 57. Multi-module architecture
  58. 58. It works!
  59. 59. feature3feature1 feature2 App
  60. 60. feature3feature1 feature2 AppFeature1App
  61. 61. feature1 feature2 Feature1App feature3 App
  62. 62. Activity Repository Api UseCase ViewModel
  63. 63. entitiesentitiesentitiesentities domain repository UI data source presenter Activity Repository Api UseCase ViewModel
  64. 64. entitiesentitiesentitiesentities domain repository UI data source presenter Activity Repository Api UseCase ViewModel
  65. 65. RepositoryImplRepositoryUseCase
  66. 66. domain data UseCase RepositoryImpl Repository
  67. 67. domain data Repository RepositoryImpl UseCase
  68. 68. Inversion Of Control The “I” in S.O.L.I.D.
  69. 69. https://www.youtube.com/watch?v=GlDsfq3xHvo&t=
  70. 70. domain repository Repository RepositoryImpl UseCase @Inject @Inject Module @Binds Incomplete Hilt config
  71. 71. dynamic feature3 App feature1 feature2 @EntryPoint @Component
  72. 72. Wrappingup Dependency Injection on classes we can instantiate @Inject, @Provides and @Binds Easy setup on classes instantiated by the framework @AndroidEntryPoint and @HiltAndroidApp Testability @HiltAndroidTest and HiltAndroidRule
  73. 73. Hilt is definitely a Dependency Injection framework
  74. 74. Hilt is definitely a Dependency Injection framework (even if you can use an EntryPoint as a Service Locator)
  75. 75. Links&contacts Hilt documentation dagger.dev/hilt/ Android Developers - Dependency injection with Hilt developer.android.com/training/dependency-injection/hilt-android Android Developers - Hilt testing guide developer.android.com/training/dependency-injection/hilt-testing Manuel Vivo - Dagger and Hilt navigation support in Android Studio medium.com/androiddevelopers/dagger-navigation-support-in-android-studio-49aa5d149ec9 Fabio Collini - Dagger dependencies beyond the basics proandroiddev.com/dagger-dependencies-beyond-the-basics-53474e48f932 @fabioCollini linkedin.com/in/fabiocollini github.com/fabioCollini medium.com/@fabioCollini
  76. 76. THANKS FOR YOUR ATTENTION
  77. 77. QUESTIONS? @fabioCollini

×