Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Independent Features - Powered by Dagger Extensions

Independent Features - Powered by Dagger Extensions

Every application consists of high level features which can be built completely isolated and independent, but what are the benefits of having independent features and when does it make sense to have them?

This talk will not only answer those questions, but also show why this was not entirely possible with vanilla Dagger and how new extensions both Hilt and Anvil (previously known as Hepheastus) solve this problem. You will see these libraries in action and learn about one of their core benefits.

- Presented in DevFest Italia (17.10.2020) - https://youtu.be/U9GvDJvTejI
- Presented in MobileOptimized (07.11.2020) - https://youtu.be/069TN7oDd9M

Yahya Bayramoğlu

October 17, 2020
Tweet

More Decks by Yahya Bayramoğlu

Other Decks in Programming

Transcript

  1. Why Independent Features? Technical Aspect • Clear separation • Put

    more thoughts in development • Less entangled codebase Practical Aspect • Ownership • Easy to reuse • Isolated build
  2. Solution? @ApplicationScope @Component( modules = arrayOf( NetworkModule::class, FirebaseModule::class, MapProviderModule::class, ),

    dynamicModuleKey = arrayOf( "Feature1", "Feature2" ) ) interface ApplicationComponent
  3. Why Anvil and Hilt? What do they bring to the

    table? Inverse the relation between Component and Module
  4. Anvil Application Base Feature1 Define shared scopes Define shared abstractions

    Define feature scope & component Module to contribute ApplicationComponent Create ApplicationComponent * Binding for components
  5. Anvil Base @Scope @MustBeDocumented @Retention annotation class ApplicationScope data class

    Feature( val title: String, val deeplinkHost: String ) interface FeatureDependencyProvider { fun <T> dependencies(): T } interface ApplicationPlugin { fun apply(application: Application) }
  6. Anvil Base @ContributesTo(ApplicationScope::class) @Module class BaseModule { @Provides @ElementsIntoSet fun

    provideFeatureSet(): Set<Feature> = emptySet() @Provides @ElementsIntoSet fun provideApplicationPluginSet(): Set<ApplicationPlugin> = emptySet() }
  7. Anvil Feature 1 @Scope @MustBeDocumented @Retention annotation class Feature1Scope @ContributesTo(ApplicationScope::class)

    interface Feature1Dependencies { fun sampleSharedLogger(): SampleSharedLogger fun depsWithAppLifecycle(): DepsWithAppLifecycle }
  8. Anvil Feature 1 @Feature1Scope @MergeComponent( scope = Feature1Scope::class, dependencies =

    [Feature1Dependencies::class] ) interface Feature1Component
  9. Anvil Feature 1 @ContributesTo(ApplicationScope::class) @Module class Feature1ModuleToApplication { @Provides @IntoSet

    fun provideFeature1ApplicationPlugin(): ApplicationPlugin = Feature1ApplicationPlugin() }
  10. Anvil Feature 1 @ContributesTo(ApplicationScope::class) @Module class Feature1ModuleToApplication { @Provides @ApplicationScope

    fun provideDepsWithAppLifecycle(application: Application): DepsWithAppLifecycle = DepsWithAppLifecycle(application) }
  11. Anvil Generated @Component( modules = { Feature1ModuleToApplication.class, Feature2ModuleToApplication.class, BaseModule.class },

    dependencies = {}) @ApplicationScope public abstract interface ApplicationComponent extends Feature1Dependencies, Feature2Dependencies { your_module/build/tmp/kapt3/stubs/...
  12. Anvil Application class SampleApplication : Application() { val applicationComponent =

    lazy { DaggerApplicationComponent.builder() .application(this) .build() } }
  13. Anvil Application class SampleApplication : Application() { @Inject lateinit var

    applicationPlugins: Set<@JvmSuppressWildcards ApplicationPlugin> override fun onCreate() { super.onCreate() applicationComponent.value.inject(this) applicationPlugins.forEach { it.apply(this) } } }
  14. Anvil Application class SampleApplication : Application(), FeatureDependencyProvider { @Suppress("UNCHECKED_CAST") override

    fun <T> dependencies(): T { return applicationComponent.value as? T ?: throw IllegalStateException( "Feature does not provide" + " its dependencies to the ApplicationScope.") } }
  15. Hilt Application Base Feature1 Define shared abstractions Module to contribute

    ApplicationComponent Add HiltAndroidApp annotation
  16. Hilt Base data class Feature( val title: String, val deeplinkHost:

    String ) interface ApplicationPlugin { fun apply(application: Application) }
  17. Hilt Base @InstallIn(ApplicationComponent::class) @Module class BaseModule { @Provides @ElementsIntoSet fun

    provideFeatureSet(): Set<Feature> = emptySet() @Provides @ElementsIntoSet fun provideApplicationPluginSet(): Set<ApplicationPlugin> = emptySet() }
  18. Hilt Feature 1 @InstallIn(ApplicationComponent::class) @Module class Feature1ModuleToApplication { @Provides @IntoSet

    fun provideFeature1ApplicationPlugin(): ApplicationPlugin = Feature1ApplicationPlugin() }
  19. Hilt Feature 1 @InstallIn(ApplicationComponent::class) @Module class Feature1ModuleToApplication { @Provides @Singleton

    fun provideDepsWithAppLifecycle(application: Application): DepsWithAppLifecycle = DepsWithAppLifecycle(application) }
  20. Hilt Application @HiltAndroidApp class SampleApplication : Application() { @Inject lateinit

    var applicationPlugins: Set<@JvmSuppressWildcards ApplicationPlugin> override fun onCreate() { super.onCreate() applicationPlugins.forEach { it.apply(this) } } }
  21. Thank you! • “Modularise in Structure” - 2019 https://bit.ly/3nBbslG •

    “Building Features by Independent Dagger Components” - 2018 https://bit.ly/36TsXba • About the ApplicationPlugin implementation https://bit.ly/3129aCx • Github repo for the sample application https://github.com/yayaa/IndependentFeatures @yahyabayramoglu