Share
Embed
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.
Video: https://www.droidcon.com/media-detail?video=357540042
Nelson Osacky
July 02, 2019
Transcript
182 slides
-
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 -
public class Store {a private Item item; public Store()a{ item
= new ItemImpl1(); }a }b -
public class Store {a private Item item; public Store( )a{
itema=bnew ItemImpl1(); }a }b -
public class Store {a private Item item; public Store( Itembitem
)a{ this.item = item; }a }b -
Spring <bean id="item1" class="com.soundcloud.ItemImpl1" /> <bean id="store" class="com.soundcloud.Store"> <constructor-arg type="ItemImpl1"
index="0" name="item" ref="item1" /> </bean> -
Guice public class Store {c private Item item; public Store(Item
item) {d this.item = item; }a }b -
Guice public class Store {c private Item item; @Inject public
Store(Item item) {d this.item = item; }a }b -
Dagger public class Store {c private Item item; @Inject public
Store(Item item) {d this.item = item; }a }b -
Dagger @Module public class PlayerModule { @Provides public PlayerProvider providePlayerProvider(AppFeatures
appFeatures) { return new PlayerProviderImpl(appfeatures); } } -
Super rough comparison (not to scale) 0 25 50 75
100 Guice Dagger 1 Dagger 2 Compile Time Runtime -
@Component public interface ApplicationComponent {b @Component.Factory interface Builder {b} }a
DaggerApplicationComponent .factory() .create(this) -
@Component public interface ApplicationComponent {b void inject(MyApplication application); @Component.Factory interface
Builder {b} }a DaggerApplicationComponent .factory() .create(this) -
@Component public interface ApplicationComponent {b void inject(MyApplication application); @Component.Factory interface
Builder {b} }a DaggerApplicationComponent .factory() .create(this) .inject(this) -
class MyApplication : Application(), HasAndroidInjector { override fun onCreate() {
super.onCreate() DaggerApplicationComponent .factory() .create(this) .inject(this) } } -
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( ) { -
public final class DaggerApplicationComponent {a public static ApplicationComponent.Builder factory() {b
return DaggerReflect.factory(ApplicationComponent.Builder.class); }c }d -
public final class DaggerApplicationComponent {a public static ApplicationComponent.Builder factory() {b
return DaggerReflect.factory(ApplicationComponent.Builder.class); }c }d Generated by Dagger Reflect -
public final class DaggerReflect {a public static <F> F factory(Class<F>
factoryClass) {b }c }d -
public final class DaggerReflect {a public static <F> F factory(Class<F>
factoryClass) {b }c }d In Dagger Reflect Library -
public final class DaggerReflect {a public static <F> F factory(Class<F>
factoryClass) {b return ComponentFactoryInvocationHandler.forComponentFactory(factoryClass); }c }d In Dagger Reflect Library -
public interface InvocationHandler { public Object invoke(Object proxy, Method method,
Object[] args) } java.lang.Reflect -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
reflect-compiler Generates the glue to link your code to the
reflection based dagger implementation -
@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"; } } } -
@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); } -
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 -
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 -
@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); } -
private void ignoreReflectionBackend() { assumeTrue("Not yet implemented for reflection backend",
backend != Backend.REFLECT); } -
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' } -
class MyApplication : Application(), HasAndroidInjector { override fun onCreate() {
super.onCreate() DaggerApplicationComponent .factory() .create(this) .inject(this) } } -
implementation deps.dagger.dagger kapt deps.dagger.compiler private fun useDaggerReflect() : Boolean {
return properties["dagger.reflect"] == "true") } -
implementation deps.dagger.dagger if ( useDaggerReflect() ) { implementation deps.dagger.reflectRuntime kapt
deps.dagger.daggerReflectCompiler } else { kapt deps.dagger.compiler } -
implementationadeps.dagger.dagger implementationbdeps.dagger.daggerAndroid if (useDaggerReflect()) { implementation deps.dagger.reflectRuntime kapt deps.dagger.daggerReflectCompiler }celse
{a kapt deps.dagger.compiler kapt deps.dagger.daggerAndroidCompiler }b -
Swap Dagger for Dagger Reflect in every module and for
Dagger Android -
Swap Dagger for Dagger Reflect in every module and for
Dagger Android and for every new module -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
• 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 -
• Match Dagger with Features • Improve Error Messages •
Improve Runtime Performance • Kotlin Dagger Reflect -
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/ -
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 -
• 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 -
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/