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

Dagger Reflect - The circle from runtime to compile time and back again

Dagger Reflect - The circle from runtime to compile time and back again

Back in the day Java dependency injection frameworks were purely runtime based like Spring and Guava. Dagger 1 introduced a compile time component as well as compile time safety and Dagger 2 took the concept even further. Now we're going back to runtime with Dagger Reflect in an effort to improve local developer build speeds. This talk will cover the reasons for building dagger reflect, how it works under the hood, how to use it in your project as well as how it is developed.

Presented at BerlinDroid Aug 28, 2019
https://www.meetup.com/berlindroid/events/ktlzcryzlblc/

Video here: https://www.droidcon.com/media-detail?video=357540042

Nelson Osacky

August 28, 2019
Tweet

More Decks by Nelson Osacky

Other Decks in Technology

Transcript

  1. Me • Android for 9 years • Chocolate and Running

    • Previously at Square in SF on Build Tools • Currently Working at SoundCloud in Berlin • Maintainer of Fladle - Gradle Plugin for Firebase Test Lab https://github.com/runningcode/fladle • Gradle Doctor - https://github.com/runningcode/gradle- doctor
  2. Guice public class Store {c private Item item; @Inject public

    Store(Item item) {d this.item = item; }a }b
  3. Dagger public class Store {c private Item item; @Inject public

    Store(Item item) {d this.item = item; }a }b
  4. Super rough comparison (not to scale) 0 25 50 75

    100 Guice Dagger 1 Dagger 2 Compile Time Runtime
  5. @Component public interface ApplicationComponent {b void inject(MyApplication application); @Component.Factory interface

    Builder {b} }a DaggerApplicationComponent .factory() .create(this) .inject(this)
  6. class MyApplication : Application(), HasAndroidInjector { override fun onCreate() {

    super.onCreate() DaggerApplicationComponent .factory() .create(this) .inject(this) } }
  7. public final class DaggerApplicationComponent { private void initialize(final ApplicationModule applicationModuleParam,

    final FacebookModule facebookModuleParam, final ApiModule apiModuleParam, final AppFeaturesModule appFeaturesModuleParam, final. BaseAnalyticsModule baseAnalyticsModuleParam, final AdAnalyticsModule adAnalyticsModuleParam, final AppBoyModule appBoyModuleParam, final Application applicationParam) { this.userTopTracksFragmentSubcomponentFactoryProvider = new Provider<UserTopTracksModule_BindUserTopTracksFragment.UserTopTracksFragmentSubcomponent.Factory>() { @Override public UserTopTracksModule_BindUserTopTracksFragment.UserTopTracksFragmentSubcomponent.Factory get( ) { return new UserTopTracksFragmentSubcomponentFactory();} }; this.soundRecorderServiceSubcomponentFactoryProvider = new Provider<CreatorsModule_ProvidesSoundRecorder.SoundRecorderServiceSubcomponent.Factory>() { @Override public CreatorsModule_ProvidesSoundRecorder.SoundRecorderServiceSubcomponent.Factory get() { return new SoundRecorderServiceSubcomponentFactory();} }; this.recordFragmentSubcomponentFactoryProvider = new Provider<CreatorsModule_ProvidesRecordFragment.RecordFragmentSubcomponent.Factory>() { @Override public CreatorsModule_ProvidesRecordFragment.RecordFragmentSubcomponent.Factory get() { return new RecordFragmentSubcomponentFactory();} }; this.recordAppWidgetProviderSubcomponentFactoryProvider = new Provider<CreatorsModule_ProvidesRecordAppWidgetProvider.RecordAppWidgetProviderSubcomponent.Factory>() { @Override public CreatorsModule_ProvidesRecordAppWidgetProvider.RecordAppWidgetProviderSubcomponent.Factory get( ) { return new RecordAppWidgetProviderSubcomponentFactory();} }; this.homescreenWidgetBroadcastReceiverSubcomponentFactoryProvider = new Provider<HomescreenWidgetModule_HomescreenWidgetBroadcastReceiver.HomescreenWidgetBroadcastReceiverSubcomponent.Factory>() { @Override public HomescreenWidgetModule_HomescreenWidgetBroadcastReceiver.HomescreenWidgetBroadcastReceiverSubcomponent.Factory get( ) { return new HomescreenWidgetBroadcastReceiverSubcomponentFactory();} }; this.mainActivitySubcomponentFactoryProvider = new Provider<ActivityBuilder_BindMainActivity.MainActivitySubcomponent.Factory>() { @Override public ActivityBuilder_BindMainActivity.MainActivitySubcomponent.Factory get() { return new MainActivitySubcomponentFactory();} }; this.launcherActivitySubcomponentFactoryProvider = new Provider<ActivityBuilder_BindLauncherActivity.LauncherActivitySubcomponent.Factory>() { @Override public ActivityBuilder_BindLauncherActivity.LauncherActivitySubcomponent.Factory get() { return new LauncherActivitySubcomponentFactory();} }; this.resolveActivitySubcomponentFactoryProvider = new Provider<ActivityBuilder_BindResolveActivity.ResolveActivitySubcomponent.Factory>() { @Override public ActivityBuilder_BindResolveActivity.ResolveActivitySubcomponent.Factory get() { return new ResolveActivitySubcomponentFactory();} }; this.changeStorageLocationActivitySubcomponentFactoryProvider = new Provider<ActivityBuilder_BindChangeLocationStorageActivity.ChangeStorageLocationActivitySubcomponent.Factory>() { @Override public ActivityBuilder_BindChangeLocationStorageActivity.ChangeStorageLocationActivitySubcomponent.Factory get( ) { return new ChangeStorageLocationActivitySubcomponentFactory();} }; this.fullScreenVideoActivitySubcomponentFactoryProvider = new Provider<ActivityBuilder_BindFullScreenVideoActivity.FullScreenVideoActivitySubcomponent.Factory>() { @Override public ActivityBuilder_BindFullScreenVideoActivity.FullScreenVideoActivitySubcomponent.Factory get( ) { return new FullScreenVideoActivitySubcomponentFactory();} }; this.goOnboardingActivitySubcomponentFactoryProvider = new Provider<ActivityBuilder_BindGoOnboardingActivity.GoOnboardingActivitySubcomponent.Factory>() { @Override public ActivityBuilder_BindGoOnboardingActivity.GoOnboardingActivitySubcomponent.Factory get( ) {
  8. public final class DaggerApplicationComponent {a public static ApplicationComponent.Builder factory() {b

    return DaggerReflect.factory(ApplicationComponent.Builder.class); }c }d
  9. public final class DaggerApplicationComponent {a public static ApplicationComponent.Builder factory() {b

    return DaggerReflect.factory(ApplicationComponent.Builder.class); }c }d Generated by Dagger Reflect
  10. public final class DaggerReflect {a public static <F> F factory(Class<F>

    factoryClass) {b }c }d In Dagger Reflect Library
  11. public final class DaggerReflect {a public static <F> F factory(Class<F>

    factoryClass) {b return ComponentFactoryInvocationHandler.forComponentFactory(factoryClass); }c }d In Dagger Reflect Library
  12. static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass

    = requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library
  13. static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass

    = requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library
  14. static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass

    = requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library
  15. static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass

    = requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library
  16. static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass

    = requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library
  17. static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass

    = requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library
  18. static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass

    = requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library
  19. static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass

    = requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library
  20. static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass

    = requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library
  21. static ComponentScopeBuilder buildComponent(Class<?> componentClass) {a Component component = requireAnnotation(componentClass, Component.class);

    Set<Annotation> scopeAnnotation = findScopes(componentClass.getAnnotations()); return create(component.modules(), component.dependencies(), scopeAnnotation, null); }b In Dagger Reflect Library
  22. static ComponentScopeBuilder buildComponent(Class<?> componentClass) {a Component component = requireAnnotation(componentClass, Component.class);

    Set<Annotation> scopeAnnotation = findScopes(componentClass.getAnnotations()); return create(component.modules(), component.dependencies(), scopeAnnotation, null); }b In Dagger Reflect Library
  23. static ComponentScopeBuilder buildComponent(Class<?> componentClass) {a Component component = requireAnnotation(componentClass, Component.class);

    Set<Annotation> scopeAnnotation = findScopes(componentClass.getAnnotations()); return create(component.modules(), component.dependencies(), scopeAnnotation, null); }b In Dagger Reflect Library
  24. static ComponentScopeBuilder buildComponent(Class<?> componentClass) {a Component component = requireAnnotation(componentClass, Component.class);

    Set<Annotation> scopeAnnotation = findScopes(componentClass.getAnnotations()); return create(component.modules(), component.dependencies(), scopeAnnotation, null); }b In Dagger Reflect Library
  25. reflect-compiler Generates the glue to link your code to the

    reflection based dagger implementation
  26. @Test public void componentProvider() {a ComponentProvider component = backend.create(ComponentProvider.class); assertThat(component.string()).isEqualTo("foo");

    }b @Component(modules = ComponentProvider.Module1.class) interface ComponentProvider { String string(); @Module abstract class Module1 { @Provides static String string() { return "foo"; } } }
  27. @RunWith(Parameterized.class) public final class IntegrationTest { @Parameters(name = "{0}") public

    static Object[] parameters() { return Backend.values(); } @Parameter public Backend backend = backend.create(ComponentProvider.class); }
  28. enum Backend {a abstract <C> C create(Class<C> componentClass); REFLECT {b

    @Overridea <F>aFacreate(Class<F>acomponentClass) { returnaDaggerReflect.create(componentClass); }c },d CODEGEN }e
  29. enum Backend {a abstract <C> C create(Class<C> componentClass); REFLECT {b

    @Overridea <F>aFacreate(Class<F>acomponentClass) { returnaDaggerReflect.create(componentClass); }c },d CODEGEN { @Override <F> F create(Class<F> componentClass) { return DaggerCodegen.create(componentClass); } }; }e
  30. @RunWith(Parameterized.class) public final class IntegrationTest { @Parameters(name = "{0}") public

    static Object[] parameters() { return Backend.values(); } @Parameter public Backend backend = backend.create(ComponentProvider.class); }
  31. test.filter { // dagger-reflect does not produce the exact same

    behavior as dagger-compiler for @Reusable. excludeTest 'dagger.functional.ReusableTest', null // TODO reflect bug! Need something like ByteBuddy for proxying classes at runtime. excludeTest 'dagger.functional.builder.BuilderBindsInstanceParameterTest', null // TODO reflect bug! Generics don't work well. excludeTest 'dagger.functional.GenericTest', 'complexGenerics' }
  32. class MyApplication : Application(), HasAndroidInjector { override fun onCreate() {

    super.onCreate() DaggerApplicationComponent .factory() .create(this) .inject(this) } }
  33. implementation deps.dagger.dagger if ( useDaggerReflect() ) { implementation deps.dagger.reflectRuntime kapt

    deps.dagger.daggerReflectCompiler } else { kapt deps.dagger.compiler }
  34. Swap Dagger for Dagger Reflect in every module and for

    Dagger Android and for every new module
  35. Swap Dagger for Dagger Reflect in every module and for

    Dagger Android and for every new module and don’t forget the lint check
  36. configurations.all config@ {a dependencies.all {b //bIf we depend on the

    daggerbruntime, also add the daggerbreflect runtime. ifb(group == daggerGroupId && name == "dagger") {c dependencies {d add([email protected], "$com.jakewharton.dagger:dagger-reflect:${extension.daggerReflectVersion}") }e }f }g resolutionStrategy {h dependencySubstitution {o // Substitute dagger compiler for dagger reflect compiler. substitute( module("$com.google.dagger:dagger-compiler“) ).apply { with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) because("We want to build faster.") }p substitute(module("$com.google.dagger:dagger-android-processor")) .with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) }q }r }s
  37. configurations.all config@ {a dependencies.all {b //bIf we depend on the

    daggerbruntime, also add the daggerbreflect runtime. ifb(group == daggerGroupId && name == "dagger") {c dependencies {d add([email protected], "$com.jakewharton.dagger:dagger-reflect:${extension.daggerReflectVersion}") }e }f }g }s
  38. configurations.all config@ {a dependencies.all {b //bIf we depend on the

    daggerbruntime, also add the daggerbreflect runtime. ifb(group == daggerGroupId && name == "dagger") {c dependencies {d add([email protected], "$com.jakewharton.dagger:dagger-reflect:${extension.daggerReflectVersion}") }e }f }g }s
  39. configurations.all config@ {a dependencies.all {b //bIf we depend on the

    daggerbruntime, also add the daggerbreflect runtime. ifb(group == daggerGroupId && name == "dagger") {c dependencies {d add([email protected], "$com.jakewharton.dagger:dagger-reflect:${extension.daggerReflectVersion}") }e }f }g resolutionStrategy {h dependencySubstitution {o // Substitute dagger compiler for dagger reflect compiler. substitute( module("$com.google.dagger:dagger-compiler") ).apply { with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) because("We want to build faster.") }p substitute(module("$com.google.dagger:dagger-android-processor")) .with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) }q }r }s
  40. configurations.all config@ {a resolutionStrategy {h dependencySubstitution {o // Substitute dagger

    compiler for dagger reflect compiler. substitute( module("$com.google.dagger:dagger-compiler") ).apply { with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) because("We want to build faster.") }p }q }r }s
  41. configurations.all config@ {a resolutionStrategy {h dependencySubstitution {o // Substitute dagger

    compiler for dagger reflect compiler. substitute( module("$com.google.dagger:dagger-compiler") ).apply { with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) because("We want to build faster.") }p substitute(module("$com.google.dagger:dagger-android-processor")) .with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) }q }r }s
  42. configurations.all config@ {a resolutionStrategy {h dependencySubstitution {o // Substitute dagger

    compiler for dagger reflect compiler. substitute( module("$com.google.dagger:dagger-compiler") ).apply { with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) because("We want to build faster.") }p substitute(module("$com.google.dagger:dagger-android-processor")) .with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) }q }r }s
  43. configurations.all config@ {a dependencies.all {b //bIf we depend on the

    daggerbruntime, also add the daggerbreflect runtime. ifb(group == daggerGroupId && name == "dagger") {c dependencies {d add([email protected], "$com.jakewharton.dagger:dagger-reflect:${extension.daggerReflectVersion}") }e }f }g resolutionStrategy {h dependencySubstitution {o // Substitute dagger compiler for dagger reflect compiler. substitute( module("$com.google.dagger:dagger-compiler") ).apply { with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) because("We want to build faster.") }p substitute(module("$com.google.dagger:dagger-android-processor")) .with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) }q }r }s
  44. Dagger Reflect Build clean with unique java file 1m54s 1m35s

    Clean build 31s 26s No-op build 8s 7s Make change to DI 1m8s 1m1s Micro benchmark
  45. • Slower startup time • Lazy reflection (doesn’t scan entire

    app at startup) • Different behavior than production • Not all Dagger features are supported yet • Poor error messages
  46. • Match Dagger with Features • Improve Error Messages •

    Improve Runtime Performance • Kotlin Dagger Reflect
  47. Relative build performance J genating J only K generating K

    only K & J generating K & J Time https://eng.uber.com/measuring-kotlin-build-performance/
  48. At SoundCloud • Long running side project • Small number

    of devs using it on master since last week! • Currently running Dagger Reflect behind an opt-in gradle property • Using a fork of Dagger Reflect with our supported features
  49. • Thanks to Riccardo for testing Delect and help with

    open sourcing • Thanks to my team for testing out Dagger Reflect • Thanks to SoundCloud for letting me have the time to work on this • Thanks Jake Wharton for reviewing my code and building and releasing 0.1.0
  50. More information • DIY: Build your own dependency injection library

    - Pierre- Yves Ricau - https://academy.realm.io/posts/android- pierre-yves-ricau-build-own-dependency-injection/ • Dagger Reflect: https://github.com/jakewharton/dagger- reflect • Dagger Reflect Gradle Plugin: https://github.com/ soundcloud/delect • A brief history of dependency injection http://mvpjava.com/ brief-history-dependency-injection/