Confidential + ProprietaryConfidential + Proprietary
Dagger 2
Now Even Pointier!™
Confidential + ProprietaryConfidential + Proprietary
An introduction
Confidential + Proprietary
A quick (re)introduction to dependency injection
/* With Manual DI */
class CoffeeMaker {
private final Heater heater;
private final Pump pump;
CoffeeMaker(Heater heater, Pump pump) {
this.heater = checkNotNull(heater);
this.pump = checkNotNull(pump);
}
Coffee makeCoffee() {/* … */}
}
class CoffeeMain {
public static void main(String[] args) {
Heater heater = new ElectricHeater();
Pump pump = new Thermosiphon(heater);
Coffee coffee =
new CoffeeMaker(heater, pump).makeCoffee();
}
}
/* Without DI */
class CoffeeMaker {
private final Heater heater;
private final Pump pump;
CoffeeMaker() {
this.heater = new ElectricHeater();
this.pump = new Thermosiphon(heater);
}
Coffee makeCoffee() {/* … */}
}
class CoffeeMain {
public static void main(String[] args) {
Coffee coffee =
new CoffeeMaker().makeCoffee();
}
}
Confidential + Proprietary
DI with Dagger 2
Confidential + Proprietary
Dagger 2 - Injected objects
@Inject
Thermosiphon(Heater heater) {
this.heater = heater;
}
Confidential + Proprietary
Dagger 2 - declarative configuration
@Provides
@Singleton
Heater provideHeater() {
return new ElectricHeater();
}
Confidential + Proprietary
Dagger 2 - typesafe dependency injection
@Singleton
@Component(
modules = DripCoffeeModule.class)
public interface CoffeeComponent {
CoffeeMaker maker();
}
Confidential + Proprietary
OK, but why?
Confidential + Proprietary
Consistent, efficient
instance management
Confidential + Proprietary
Dagger 2.0.1 - 2.4
Confidential + ProprietaryConfidential + Proprietary
static @Provides
methods
Confidential + Proprietary
@Provides
Heater provideHeater() {
return new ElectricHeater();
}
Confidential + Proprietary
@Provides
static Heater provideHeater() {
return new ElectricHeater();
}
Confidential + ProprietaryConfidential + Proprietary
Subcomponents
Confidential + Proprietary
Subcomponents
@Component(modules = {ServerModule.class, AuthModule.class})
interface ServerComponent {
Server server();
SessionComponent sessionComponent(SessionModule sessionModule);
}
@Subcomponent(modules = SessionModule.class)
interface SessionComponent {
SessionInfo sessionInfo();
RequestComponent requestComponent();
}
@Subcomponent(modules = {RequestModule.class, AuthModule.class})
interface RequestComponent {
RequestHandler requestHandler();
}
Confidential + ProprietaryConfidential + Proprietary
Map Multibindings
Confidential + Proprietary
Map multibindings
Confidential + Proprietary
enum MessageType {
TEXT,
PHOTO,
LOCATION,
}
@MapKey
@interface ForMessageType {
MessageType value();
}
Map multibindings
@Provides
@IntoMap
@ForMessageType(TEXT)
Renderer contributeTextRenderer() { /*…*/ }
@Provides
@IntoMap
@ForMessageType(PHOTO)
Renderer contributePhotoRenderer() { /*…*/ }
@Provides
@IntoMap
@ForMessageType(LOCATION)
Renderer contributeVideoRenderer() { /*…*/ }
Confidential + Proprietary
Map multibindings
@Inject Map<MessageType, Provider<Renderer>> renderers;
// …
for (Message message : messages) {
renderers.get(message.type()).get().render(message);
}
Confidential + ProprietaryConfidential + Proprietary
Producers
Confidential + Proprietary
Producers
@ProducerModule(includes = UserModule.class)
final class UserResponseModule {
@Produces
static ListenableFuture<UserData>
lookUpUserData(
User user, UserDataStub stub) {
return stub.lookUpData(user);
}
@Produces
static Html renderHtml(
UserData data,
UserHtmlTemplate template) {
return template.render(data);
}
}
@Module
final class ExecutorModule {
@Provides
@Production
static Executor executor() {
return Executors.newCachedThreadPool();
}
}
@ProductionComponent(modules =
UserResponseModule.class)
interface UserResponseComponent {
ListenableFuture<Html> html();
}
Confidential + ProprietaryConfidential + Proprietary
Improved error
messages
Confidential + Proprietary
Error messages
error: coffee.Pump cannot be provided without an
@Provides-annotated method.
CoffeeMaker maker();
^
coffee.Pump is injected at
coffee.CoffeeMaker.<init>(…, pump)
coffee.CoffeeMaker is provided at
coffee.CoffeeApp.CoffeeComponent.maker()
Confidential + ProprietaryConfidential + Proprietary
Project
Management
Confidential + Proprietary
Development within Google
● Millions of test targets are executed for
every Dagger change
● Constant performance testing and
monitoring from JVM and Android projects
● New APIs are developed against a huge,
searchable repository of real usages
● It is an invaluable, proven model from which
we have gotten projects like Guava
Working with GitHub
● We haven't done as well as we need to
○ Google builds from HEAD, but the rest of the
world doesn't
○ Syncs from Google were big, unmanaged
dumps of changes with no compatibility
guarantees
● New release policy makes sure that there is
a versioned release about every 2 weeks
○ Cultivated change log
○ Compatibility guarantees
Project management
Confidential + Proprietary
Dagger 2.5+
Confidential + ProprietaryConfidential + Proprietary
Performance
enhancements
Confidential + Proprietary
The generated code
Confidential + Proprietary
Unnecessary Providers
Confidential + Proprietary
Retained references
Confidential + ProprietaryConfidential + Proprietary
@Binds
Confidential + Proprietary
@Provides
static Pump providePump(Thermosiphon pump) {
return pump;
}
Confidential + Proprietary
@Binds
abstract Pump bindPump(Thermosiphon pump);
Confidential + ProprietaryConfidential + Proprietary
Memory
management tools
Confidential + ProprietaryConfidential + Proprietary
Considerations
Mobile devices mean we need to be judicious
● Number of allocations
● Long-term memory footprint
Confidential + ProprietaryConfidential + Proprietary
Unscoped
A new instance for every injection site
● Number of allocations
● Long-term memory footprint
Confidential + ProprietaryConfidential + Proprietary
@Scope
Only one instance per component
● Number of allocations
● Long-term memory footprint
Confidential + ProprietaryConfidential + Proprietary
● Number of allocations
● Long-term memory footprint
@Reusable
Instances can be reused, but how and when isn't
a functional requirement
Confidential + ProprietaryConfidential + Proprietary
Pressure-sensitive scopes
There should only be one instance, but if there is
memory pressure make it eligible for garbage
collection
● Number of allocations
● Long-term memory footprint
Confidential + ProprietaryConfidential + Proprietary
Easier
subcomponents
Confidential + Proprietary
An Application component
@Singleton
@Component(modules = ApplicationModule.class)
interface MyApplicationComponent {
MyApplication inject(MyApplication application);
@Component.Builder
interface Builder {
Builder applicationModule(ApplicationModule module);
MyApplicationComponent build();
}
}
Confidential + Proprietary
@ActivityScoped
@Subcomponent(
modules = ActivityModule.class)
interface MainActivitySubcomponent {
MainActivity injectMainActivity(
MainActivity activity);
@Subcomponent.Builder
interface Builder {
Builder activityModule(
ActivityModule activityModule);
MainActivitySubcomponent build();
}
}
An activity subcomponent today
@Singleton
@Component(modules = ApplicationModule.
class)
interface MyApplicationComponent {
MyApplication inject(
MyApplication application);
MainActivitySubcomponent.Builder
newMainActivitySubcomponentBuilder();
/* … */
}
Confidential + Proprietary
What if we didn't have to have that method?
public final class MyAppication extends Application {
@Inject
MainActivitySubcomponent.Builder builder;
/* … */
}
Confidential + Proprietary
First, a common interface
interface ActivityComponentBuilder<A extends Activity> {
ActivityComponentBuilder<A> activityModule(ActivityModule activityModule);
MembersInjector<A> build();
}
@ActivityScoped
@Subcomponent(modules = ActivityModule.class)
interface MainActivitySubcomponent {
MainActivity injectMainActivity(MainActivity activity);
@Subcomponent.Builder
interface Builder extends ActivityComponentBuilder<ActivityModule> {}
}
Confidential + Proprietary
Second, map multibinder
@Module
abstract class MainActivitySubcomponentModule {
@Binds @IntoMap @ActivityKey(MainActivity.class)
abstract ActivityComponentBuilder<?> bindBuilder(
MainActivitySubcomponent.Builder<MainActivity> impl);
}
Confidential + Proprietary
Finally, back to MyApplication
interface HasActivitySubcomponentBuilders {
<A extends Activity> ActivityComponentBuilder<A> getActivityComponentBuilder(
Class<? extends Activity> activityClass);
}
class MyApplication extends Application implements HasActivitySubcomponentBuilders {
@Inject Map<Class<? extends Activity, ActivityComponentBuilder<?>> activityComponentBuilders;
@Override
public <A extends Activity> ActivityComponentBuilder<A> getActivityComponentBuilder(
Class<? extends Activity> activityClass) {
checkArgument(activityComponentBuilders.containsKey(activityClass));
return (ActivityComponentBuilder<A>) activityComponentBuilders.get(activityClass);
}
}
Confidential + Proprietary
Q & A

MCE^3 - Gregory Kick - Dagger 2

  • 1.
    Confidential + ProprietaryConfidential+ Proprietary Dagger 2 Now Even Pointier!™
  • 2.
    Confidential + ProprietaryConfidential+ Proprietary An introduction
  • 3.
    Confidential + Proprietary Aquick (re)introduction to dependency injection /* With Manual DI */ class CoffeeMaker { private final Heater heater; private final Pump pump; CoffeeMaker(Heater heater, Pump pump) { this.heater = checkNotNull(heater); this.pump = checkNotNull(pump); } Coffee makeCoffee() {/* … */} } class CoffeeMain { public static void main(String[] args) { Heater heater = new ElectricHeater(); Pump pump = new Thermosiphon(heater); Coffee coffee = new CoffeeMaker(heater, pump).makeCoffee(); } } /* Without DI */ class CoffeeMaker { private final Heater heater; private final Pump pump; CoffeeMaker() { this.heater = new ElectricHeater(); this.pump = new Thermosiphon(heater); } Coffee makeCoffee() {/* … */} } class CoffeeMain { public static void main(String[] args) { Coffee coffee = new CoffeeMaker().makeCoffee(); } }
  • 4.
  • 5.
    Confidential + Proprietary Dagger2 - Injected objects @Inject Thermosiphon(Heater heater) { this.heater = heater; }
  • 6.
    Confidential + Proprietary Dagger2 - declarative configuration @Provides @Singleton Heater provideHeater() { return new ElectricHeater(); }
  • 7.
    Confidential + Proprietary Dagger2 - typesafe dependency injection @Singleton @Component( modules = DripCoffeeModule.class) public interface CoffeeComponent { CoffeeMaker maker(); }
  • 8.
  • 9.
    Confidential + Proprietary Consistent,efficient instance management
  • 10.
  • 11.
    Confidential + ProprietaryConfidential+ Proprietary static @Provides methods
  • 12.
    Confidential + Proprietary @Provides HeaterprovideHeater() { return new ElectricHeater(); }
  • 13.
    Confidential + Proprietary @Provides staticHeater provideHeater() { return new ElectricHeater(); }
  • 14.
    Confidential + ProprietaryConfidential+ Proprietary Subcomponents
  • 15.
    Confidential + Proprietary Subcomponents @Component(modules= {ServerModule.class, AuthModule.class}) interface ServerComponent { Server server(); SessionComponent sessionComponent(SessionModule sessionModule); } @Subcomponent(modules = SessionModule.class) interface SessionComponent { SessionInfo sessionInfo(); RequestComponent requestComponent(); } @Subcomponent(modules = {RequestModule.class, AuthModule.class}) interface RequestComponent { RequestHandler requestHandler(); }
  • 16.
    Confidential + ProprietaryConfidential+ Proprietary Map Multibindings
  • 17.
  • 18.
    Confidential + Proprietary enumMessageType { TEXT, PHOTO, LOCATION, } @MapKey @interface ForMessageType { MessageType value(); } Map multibindings @Provides @IntoMap @ForMessageType(TEXT) Renderer contributeTextRenderer() { /*…*/ } @Provides @IntoMap @ForMessageType(PHOTO) Renderer contributePhotoRenderer() { /*…*/ } @Provides @IntoMap @ForMessageType(LOCATION) Renderer contributeVideoRenderer() { /*…*/ }
  • 19.
    Confidential + Proprietary Mapmultibindings @Inject Map<MessageType, Provider<Renderer>> renderers; // … for (Message message : messages) { renderers.get(message.type()).get().render(message); }
  • 20.
  • 21.
    Confidential + Proprietary Producers @ProducerModule(includes= UserModule.class) final class UserResponseModule { @Produces static ListenableFuture<UserData> lookUpUserData( User user, UserDataStub stub) { return stub.lookUpData(user); } @Produces static Html renderHtml( UserData data, UserHtmlTemplate template) { return template.render(data); } } @Module final class ExecutorModule { @Provides @Production static Executor executor() { return Executors.newCachedThreadPool(); } } @ProductionComponent(modules = UserResponseModule.class) interface UserResponseComponent { ListenableFuture<Html> html(); }
  • 22.
    Confidential + ProprietaryConfidential+ Proprietary Improved error messages
  • 23.
    Confidential + Proprietary Errormessages error: coffee.Pump cannot be provided without an @Provides-annotated method. CoffeeMaker maker(); ^ coffee.Pump is injected at coffee.CoffeeMaker.<init>(…, pump) coffee.CoffeeMaker is provided at coffee.CoffeeApp.CoffeeComponent.maker()
  • 24.
    Confidential + ProprietaryConfidential+ Proprietary Project Management
  • 25.
    Confidential + Proprietary Developmentwithin Google ● Millions of test targets are executed for every Dagger change ● Constant performance testing and monitoring from JVM and Android projects ● New APIs are developed against a huge, searchable repository of real usages ● It is an invaluable, proven model from which we have gotten projects like Guava Working with GitHub ● We haven't done as well as we need to ○ Google builds from HEAD, but the rest of the world doesn't ○ Syncs from Google were big, unmanaged dumps of changes with no compatibility guarantees ● New release policy makes sure that there is a versioned release about every 2 weeks ○ Cultivated change log ○ Compatibility guarantees Project management
  • 26.
  • 27.
    Confidential + ProprietaryConfidential+ Proprietary Performance enhancements
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
    Confidential + Proprietary @Provides staticPump providePump(Thermosiphon pump) { return pump; }
  • 33.
    Confidential + Proprietary @Binds abstractPump bindPump(Thermosiphon pump);
  • 34.
    Confidential + ProprietaryConfidential+ Proprietary Memory management tools
  • 35.
    Confidential + ProprietaryConfidential+ Proprietary Considerations Mobile devices mean we need to be judicious ● Number of allocations ● Long-term memory footprint
  • 36.
    Confidential + ProprietaryConfidential+ Proprietary Unscoped A new instance for every injection site ● Number of allocations ● Long-term memory footprint
  • 37.
    Confidential + ProprietaryConfidential+ Proprietary @Scope Only one instance per component ● Number of allocations ● Long-term memory footprint
  • 38.
    Confidential + ProprietaryConfidential+ Proprietary ● Number of allocations ● Long-term memory footprint @Reusable Instances can be reused, but how and when isn't a functional requirement
  • 39.
    Confidential + ProprietaryConfidential+ Proprietary Pressure-sensitive scopes There should only be one instance, but if there is memory pressure make it eligible for garbage collection ● Number of allocations ● Long-term memory footprint
  • 40.
    Confidential + ProprietaryConfidential+ Proprietary Easier subcomponents
  • 41.
    Confidential + Proprietary AnApplication component @Singleton @Component(modules = ApplicationModule.class) interface MyApplicationComponent { MyApplication inject(MyApplication application); @Component.Builder interface Builder { Builder applicationModule(ApplicationModule module); MyApplicationComponent build(); } }
  • 42.
    Confidential + Proprietary @ActivityScoped @Subcomponent( modules= ActivityModule.class) interface MainActivitySubcomponent { MainActivity injectMainActivity( MainActivity activity); @Subcomponent.Builder interface Builder { Builder activityModule( ActivityModule activityModule); MainActivitySubcomponent build(); } } An activity subcomponent today @Singleton @Component(modules = ApplicationModule. class) interface MyApplicationComponent { MyApplication inject( MyApplication application); MainActivitySubcomponent.Builder newMainActivitySubcomponentBuilder(); /* … */ }
  • 43.
    Confidential + Proprietary Whatif we didn't have to have that method? public final class MyAppication extends Application { @Inject MainActivitySubcomponent.Builder builder; /* … */ }
  • 44.
    Confidential + Proprietary First,a common interface interface ActivityComponentBuilder<A extends Activity> { ActivityComponentBuilder<A> activityModule(ActivityModule activityModule); MembersInjector<A> build(); } @ActivityScoped @Subcomponent(modules = ActivityModule.class) interface MainActivitySubcomponent { MainActivity injectMainActivity(MainActivity activity); @Subcomponent.Builder interface Builder extends ActivityComponentBuilder<ActivityModule> {} }
  • 45.
    Confidential + Proprietary Second,map multibinder @Module abstract class MainActivitySubcomponentModule { @Binds @IntoMap @ActivityKey(MainActivity.class) abstract ActivityComponentBuilder<?> bindBuilder( MainActivitySubcomponent.Builder<MainActivity> impl); }
  • 46.
    Confidential + Proprietary Finally,back to MyApplication interface HasActivitySubcomponentBuilders { <A extends Activity> ActivityComponentBuilder<A> getActivityComponentBuilder( Class<? extends Activity> activityClass); } class MyApplication extends Application implements HasActivitySubcomponentBuilders { @Inject Map<Class<? extends Activity, ActivityComponentBuilder<?>> activityComponentBuilders; @Override public <A extends Activity> ActivityComponentBuilder<A> getActivityComponentBuilder( Class<? extends Activity> activityClass) { checkArgument(activityComponentBuilders.containsKey(activityClass)); return (ActivityComponentBuilder<A>) activityComponentBuilders.get(activityClass); } }
  • 47.