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

Building Features by Independent Dagger Components

Building Features by Independent Dagger Components

Modularization has become a huge topic recently within Android Community, and we have a lot of sources to look into nowadays. But what are the limitations? How can you provide scaleable dependencies? How can you keep your dependencies small enough to keep initializations lazy? How can you isolate modules and make them completely unaware of the project and each other?

In this talk, You will learn a simple way about how to set up your dependencies within multi-module project by Dagger and a concept that can help you to make them decoupled completely. Both approaches will let you have lazy initialization of Application scope objects depending on which feature they are necessary for. During the talk, you will also discover what are the limitations of decoupling and the reasons behind it.

- Presented in MobileOptimized (07.10.2018)
https://youtu.be/kNU9jFRjOfA

Yahya Bayramoğlu

October 07, 2018
Tweet

More Decks by Yahya Bayramoğlu

Other Decks in Programming

Transcript

  1. Instant App • Runs single feature (almost) instantly • Has

    certain requirements ◦ 4 MB ◦ Deeplink ◦ Features cannot depend on other features • Very good sell point for business :)
  2. App Feature 2 Base Feature 1 Feature 3 Payment Map

    Camera Network Database Navigation Firebase Gallery
  3. Dagger2 • Dependency Injection framework • Compile time • Dagger2

    ≠ Dagger-Android ◦ Dagger2 is core ◦ Dagger-Android is addition • Scope - Component - Module
  4. Application Scope • Single instance for along with Application •

    Why do we even need? ◦ Application context ◦ Libraries require to be initialized ◦ Single instance to manage correctly • Doesn’t mean @Singleton
  5. @ApplicationScope @Component(modules = arrayOf( NetworkModule::class, DataLayerModule::class, FirebaseModule::class, MapProviderModule::class, CameraModule::class, OnlyFeature1RelatedModule::class,

    ThenFeature2Module::class, GlobalApiModule::class, // ... )) abstract class ApplicationComponent • Modules need to be defined statically • Different features need different dependencies • Initialized even though user might not use it • Makes them explicitly dependent
  6. @ApplicationScope @Component(modules = arrayOf( NetworkModule::class, DataLayerModule::class, FirebaseModule::class, MapProviderModule::class, CameraModule::class), dynamicModuleKey

    = arrayOf( "Feature1", "Feature2" )) abstract class ApplicationComponent • Appending modules with same key if exist • Decouples module dependencies
  7. Closed • Defeats almost all of the benefits of Dagger2

    • Dagger1 did it • Alternative: Interface with bindings?
  8. // @ContributesAndroidInjector generates a module that is implicitly included in

    the enclosing module private void collectImplicitlyIncludedModules( Set<ModuleDescriptor> includedModules, TypeElement moduleElement) { TypeElement contributesAndroidInjector = elements.getTypeElement("dagger.android.ContributesAndroidInjector"); if (contributesAndroidInjector == null) { return; } for (ExecutableElement method : methodsIn(moduleElement.getEnclosedElements())) { if (isAnnotationPresent(method, contributesAndroidInjector.asType())) { includedModules.add( create(elements.checkTypePresent(implicitlyIncludedModuleName(method)))); } } } Source code -> https://goo.gl/EnKjGm Dagger Core
  9. // @ContributesAndroidInjector generates a module that is implicitly included in

    the enclosing module private void collectImplicitlyIncludedModules( Set<ModuleDescriptor> includedModules, TypeElement moduleElement) { TypeElement contributesAndroidInjector = elements.getTypeElement("dagger.android.ContributesAndroidInjector"); if (contributesAndroidInjector == null) { return; } for (ExecutableElement method : methodsIn(moduleElement.getEnclosedElements())) { if (isAnnotationPresent(method, contributesAndroidInjector.asType())) { includedModules.add( create(elements.checkTypePresent(implicitlyIncludedModuleName(method)))); } } } Source code -> https://goo.gl/EnKjGm Dagger Core
  10. • Still have one core Application component ◦ so that

    we can reuse instances • Features with own Application component • Generate components only when it is needed. • Application ◦ works as proxy so that it just distributes the components. ◦ is not aware of any feature How?
  11. abstract class BaseSampleApplication : Application() { val coreApplicationComponent: CoreApplicationComponent by

    lazy { DaggerSampleCoreComponent.builder() .application(this) .build() } abstract fun feature1ApplicationComponent(): FeatureApplicationComponent abstract fun feature2ApplicationComponent(): FeatureApplicationComponent abstract fun mainApplicationComponent(): FeatureApplicationComponent } Base
  12. • Still have one core Application component ◦ so that

    we can reuse instances • Features with own Application component • Generate components only when it is needed. • Application ◦ works as proxy so that it just distributes the components. ◦ is not aware of any feature How? ✔
  13. @Feature1Scope @Component(dependencies = arrayOf(CoreApplicationComponent::class)) interface Feature1Component : FeatureApplicationComponent { companion

    object { fun create(coreApplicationComponent: CoreApplicationComponent): Feature1Component = DaggerFeature1Component.builder() .coreApplicationComponent(coreApplicationComponent) .build() } } Feature 1 internal fun Activity.getApplicationComponent() = (application as BaseSampleApplication).feature1ApplicationComponent() as Feature1Component
  14. • Still have one core Application component ◦ so that

    we can reuse instances • Features with own Application component • Generate components only when it is needed. • Application ◦ works as proxy so that it just distributes the components. ◦ is not aware of any feature How? ✔ ✔
  15. class SampleApplication : BaseSampleApplication() { private val feature1component: Feature1Component by

    lazy { Feature1Component.create(coreApplicationComponent) } override fun feature1ApplicationComponent(): FeatureApplicationComponent = feature1component } App
  16. • Still have one core Application component ◦ so that

    we can reuse instances • Features with own Application component • Generate components only when it is needed. • Application ◦ works as proxy so that it just distributes the components. ◦ is not aware of any feature How? ✔ ✔ ✔ ✔
  17. class SampleApplication : BaseSampleApplication() { private val feature1component: Feature1Component by

    lazy { Feature1Component.create(coreApplicationComponent) } override fun feature1ApplicationComponent(): FeatureApplicationComponent = feature1component } App
  18. class SampleApplication : BaseSampleApplication() { private val feature1component: Feature1Component by

    lazy { Feature1Component.create(coreApplicationComponent) } override fun feature1ApplicationComponent(): FeatureApplicationComponent = feature1component } App import com.yayandroid.feature1.Feature1Component
  19. • Still have one core Application component ◦ so that

    we can reuse instances • Features with own Application component • Generate components only when it is needed. • Application ◦ works as proxy so that it just distributes the components. ◦ is not aware of any feature How? ✔ ✔ ✔ ✔
  20. • @array/component_generators ◦ List of strings to be used as

    key for features • @string/component_generator_core ◦ Key to identify where core component generator is <meta-data android:name="@string/component_generator_feature1" android:value="com.yayandroid.componentdispatcher.sample.feature1.Feature1ComponentGenerator" />
  21. ComponentGenerator sealed class ComponentGenerator<out T: ApplicationComponent> { internal lateinit var

    application: Application abstract val component: T @Suppress("HasPlatformType") fun componentClass() = (this::class.java.genericSuperclass as ParameterizedType).actualTypeArguments[0] }
  22. abstract class CoreComponentGenerator<out T: CoreApplicationComponent> : ComponentGenerator<T>() { override val

    component: T by lazy { generate(application) } abstract fun generate(application: Application): T } abstract class FeatureComponentGenerator<out T: FeatureApplicationComponent> : ComponentGenerator<T>() { internal var coreApplicationComponent: CoreApplicationComponent? = null override val component: T by lazy { generate(application, coreApplicationComponent) } abstract fun generate(application: Application, coreApplicationComponent: CoreApplicationComponent?): T }
  23. ComponentDispatcher • Read the metadata • Parse component generator locations

    • Create a map with all generators ◦ Type as key ◦ ComponentGenerator as value • ComponentGenerator instances are created by reflection Perform a black m agic ritual
  24. class Feature1ComponentGenerator : FeatureComponentGenerator<Feature1Component>() { override fun generate(application: Application, coreApplicationComponent:

    CoreApplicationComponent?): Feature1Component = Feature1Component.create(application, coreApplicationComponent) } Feature 1
  25. class SampleApplication : Application() { private lateinit var componentDispatcher: ComponentDispatcher

    override fun onCreate() { super.onCreate() componentDispatcher = ComponentDispatcher(this) } fun getComponentDispatcher() = componentDispatcher } val Activity.componentDispatcher: ComponentDispatcher get() = (this.application as SampleApplication).getComponentDispatcher() Base
  26. • Still have one core Application component ◦ so that

    we can reuse instances • Features with own Application component • Generate components only when it is needed. • Application ◦ works as proxy so that it just distributes the components. ◦ is not aware of any feature How? ✔ ✔ ✔ ✔ ✔
  27. class Feature1Activity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) componentDispatcher.get<Feature1Component>().inject(this) } }
  28. Thank you! • Sample for first implementation ◦ https://github.com/yayaa/PolyComponentSample •

    ComponentDispatcher ◦ https://github.com/yayaa/ComponentDispatcher • Blog Post ◦ https://medium.com/@yahyabayramoglu/building-features-by-independe nt-dagger-components-f634e55b9f07