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

Everything you need to know about Dagger 2

Everything you need to know about Dagger 2

Dagger v2.2 presentation I gave to internal teams @CapitalOne

Tips and tricks I have learnt over time.

Mayank Mehta

February 28, 2017
Tweet

More Decks by Mayank Mehta

Other Decks in Technology

Transcript

  1. What is Dependency Injection? “The Dependency Injection pattern is a

    specialized version of the Inversion of Control pattern where the concern being inverted is the process of obtaining the needed dependency.” - Microsoft Dagger is Dependency Injection Framework!
  2. Using Dependency Injection public LaunchActivityViewModel(TripRepository tripRepository) { this.tripRepository = tripRepository;

    } public LaunchActivityViewModel() { this.tripRepository = new TripRepositoryImpl(new CloudTripDataStore(new ModelMapper(), new LogExporter()); }
  3. Pros – Dependency Injection • Clean and modular code. •

    Makes code more readable. • Easier to write tests.
  4. Pros – Dagger 2 • Compile time code generation and

    validation (easier to follow through). • No use of reflection as a result code is easy to trace and better performance almost 13% compared to Dagger 1 (according to Google’s benchmark).
  5. How to include Dagger 2 library? // Android Gradle plugin

    < 2.2 apply plugin: 'com.neenbedankt.android-apt' // Dagger 2 compile 'com.google.dagger:dagger:2.2' apt 'com.google.dagger:dagger-compiler:2.2' provided 'javax.annotation:jsr250-api:1.0'
  6. Gratis Pilot • Our team created a separate android application

    to evaluate location vendors, to see how accurately they can identify a store when user walks into it. • Code snippets in this presentation are from that application. • Code - https://github.kdc.capitalone.com/android-wallet/GratisPilot
  7. Key concepts • @Inject • @Module • @Scope • @Lazy

    • @Named & @Qualifier • @Component • @Subcomponent
  8. @Inject • Annotated constructor tells dagger how to create instance

    of that class and requests to resolve it’s dependency.
  9. Constructor Injection public class DemoClass { private DummyExporter dummyExporter; @Inject

    public DemoClass(DummyExporter dummyExporter) { this.dummyExporter = dummyExporter; } }
  10. Field Injection public class DemoClass { @Inject StatusValidator statusValidator; private

    DummyExporter dummyExporter; @Inject public DemoClass(DummyExporter dummyExporter) { this.dummyExporter = dummyExporter; } }
  11. @Inject • Annotated constructor tells dagger how to create instance

    of that class and requests to resolve it’s dependency. • Can only be applied to Field (non-private & non-static), Constructor and Method. • Injection order : Constructor, Field and Method. • Only one constructor of the class can be annotated.
  12. Key concepts • @Inject • @Module • @Scope • @Lazy

    • @Named & @Qualifier • @Component • @Subcomponent
  13. @Module • Provides dependency for the graph. • Annotate method

    with @Provides to create provider method binding.
  14. @Module - Remember • Don’t pollute your modules. • Organize

    your modules. • Publish binding – functionality used across the app. • Internal binding – all that are not-publish binding or dependency use to construct publish binding.
  15. Published Binding @Module public class TripModule { @Provides TripRepository providesTripRepository(Realm

    realm, CloudTripDataStore cloudTripDataStore) { return new TripRepositoryImpl(realm, cloudTripDataStore); } }
  16. Internal Binding @Module public class TripModule { @Provides TripRepository providesTripRepository(Realm

    realm, CloudTripDataStore cloudTripDataStore) { return new TripRepositoryImpl(realm, cloudTripDataStore); } } Dagger will resolve CloudTripDataStore.
  17. @Module + Member injection public class DemoClass { @Inject StatusValidator

    statusValidator; @Inject public DemoClass() { } } @Module public class ApplicationModule { @Provides DemoClass demoClass() { return new DemoClass(); } } statusValidator will be null
  18. Key concepts • @Inject • @Module • @Scope • @Lazy

    • @Named & @Qualifier • @Component • @Subcomponent
  19. @Scope – Custom Annotation @Scope @Retention(RetentionPolicy.RUNTIME) public @interface PerApplication {

    } @Scope @Retention(RetentionPolicy.RUNTIME) public @interface TripScope { } @Scope @Retention(RetentionPolicy.RUNTIME) public @interface PerActivity { }
  20. Granular Scopes Fragment 1 List View Fragment 2 Detail View

    Thank You Activity Scope Application Scope Confirmation Activity Scope Trip Scope Launch Activity Scope Activity Scope Activity Scope
  21. @Scope - Example • @PerApplication – Context – SharedPreference •

    @TripScope – TripRepository - DAOs, Services • @PerActivity – ViewModels or Presenters
  22. @Scope • Dagger control creation of scoped instance. • Scope

    can be added to @Provides in @Module, @Component and class instance.
  23. Add Scope to Module @Module public class ApplicationModule { private

    final GratisPilotApplication application; public ApplicationModule(GratisPilotApplication application) { this.application = application; } @Provides @PerApplication Realm providesRealm() { RealmConfiguration config = new RealmConfiguration.Builder(application).build(); Realm.setDefaultConfiguration(config); return Realm.getDefaultInstance(); } }
  24. ScopedProvider.java – Dagger @SuppressWarnings("unchecked") // cast only happens when result

    comes from the factory @Override public T get() { // double-check idiom from EJ2: Item 71 Object result = instance; if (result == UNINITIALIZED) { synchronized (this) { result = instance; if (result == UNINITIALIZED) { instance = result = factory.get(); } } } return (T) result; }
  25. @Scope • Dagger control creation of scoped instance. • Scope

    can be added to @Provides in @Module, @Component and class instance. • @Singleton is part of javax.inject package
  26. Key concepts • @Inject • @Module • @Scope • @Lazy

    • @Named & @Qualifier • @Component • @Subcomponent
  27. @Lazy • Defer object creation. @Inject Lazy<DemoClass> demoClassLazy; // Object

    created only post following code demoClassLazy.get();
  28. Key concepts • @Inject • @Module • @Scope • @Lazy

    • @Named & @Qualifier • @Component • @Subcomponent
  29. Resolving Dependency Conflict • When you have multiple implementation of

    an interface and you have it injected via dagger, let it know which interface you are interested in.
  30. Resolving Dependency Conflict //In Module @Provides public OkHttpClient providesOkHttpClient() {

    //Regular } @Provides public OkHttpClient providesFactualOkHttpClient(String factual_key) { // OAuth } Unsatisfied dependencies for type [OkHttpClient] with qualifiers… // In class @Inject OkHttpClient regularOkHttp;
  31. @Named // Default - Regular Client @Provides @Named("regular_okhttp") public OkHttpClient

    providesOkHttpClient() { } // Explicit – OAuth Client @Provides @Named("oauth_okhttp") public OkHttpClient providesFactualOkHttpClient(@Named("factual_key") String) { } // In Class @Inject @Named("oauth_okhttp") OkHttpClient oauthOkHttp;
  32. @Named • Provide string value to binding conflict • Un

    named binding becomes default. • Refactoring is difficult L
  33. @Named Vs @Qualifier • If Object is being injected at

    many places or it’s “name” might change prefer @Qualifier.
  34. Key concepts • @Inject • @Module • @Lazy • @Scope

    • @Named & @Qualifier • @Component • @Subcomponent
  35. @Component @PerApplication @Component(modules = { ApplicationModule.class, LocationVendorModule.class, NetworkModule.class }) public

    interface ApplicationComponent { // Members-injection void inject (GratisPilotApplication gratisPilotApplication); // Provision methods Context context(); Realm realm(); }
  36. Component Implementation @PerApplication @Component(modules = { ApplicationModule.class, LocationVendorModule.class, NetworkModule.class })

    public interface ApplicationComponent { // Members-injection void inject (GratisPilotApplication gratisPilotApplication); // Provision methods Context context(); Realm realm(); } • Included modules will resolve each others dependencies.
  37. Component – Member Injection @PerApplication @Component(modules = { ApplicationModule.class, LocationVendorModule.class,

    NetworkModule.class }) public interface ApplicationComponent { // Members-injection void inject (GratisPilotApplication gratisPilotApplication); // Provision methods Context context(); Realm realm(); } • Allows Dagger to inject and resolve dependency of that class. • Generally class that you don’t control creation like Application, Activity will declared here.
  38. Component – Provision Methods @PerApplication @Component(modules = { ApplicationModule.class, LocationVendorModule.class,

    NetworkModule.class }) public interface ApplicationComponent { // Members-injection void inject (GratisPilotApplication gratisPilotApplication); // Provision methods Context context(); Realm realm(); } • Expose dependency, needed by child component.
  39. Component – Provision Methods • Allows component to expose dependency

    to child component • Dependencies are needed to be exposed in component hierarchy for child component to consume. • It increases overall method count
  40. Scoped Component @PerApplication @Component(modules = { ApplicationModule.class, LocationVendorModule.class, NetworkModule.class })

    public interface ApplicationComponent { // Members-injection void inject (GratisPilotApplication gratisPilotApplication); // Provision methods Context context(); Realm realm(); } • Scoped component forces @Modules and dependencies with @Inject to have same scope or no scope. • Unscoped components can not depend on scoped components.
  41. Child Component @TripScope @Component(dependencies = ApplicationComponent.class, modules = TripModule.class )

    public interface TripComponent { TripRepository tripRepository(); } • Child component cannot have same scope as its parent.
  42. Key concepts • @Inject • @Module • @Scope • @Lazy

    • @Named & @Qualifier • @Component • @Subcomponent
  43. @Subcomponent – Subcomponent behaves exactly like component, but implementation is

    different. – No need to explicitly expose dependency for child components.
  44. @Component @PerApplication @Component(modules = { ApplicationModule.class, LocationVendorModule.class, NetworkModule.class }) public

    interface ApplicationComponent { TripComponent plus(TripModule module); GratisPilotApplication inject(GratisPilotApplication gratisPilotApplication); }
  45. Declare child component @PerApplication @Component(modules = { ApplicationModule.class, LocationVendorModule.class, NetworkModule.class

    }) public interface ApplicationComponent { TripComponent plus(TripModule module); GratisPilotApplication inject(GratisPilotApplication gratisPilotApplication); }
  46. @Subcomponent @TripScope @Subcomponent( modules = {TripModule.class} ) public interface TripComponent

    { TripActivityComponent plus(TripActivityModule module); LaunchActivityComponent plus(LaunchActivityModule module); }
  47. @Component vs @Subcomponent • @Component – Prefer strict control over

    dependencies exposed to child components. • @Subcomponent – Loosely coupled. – Method count is a problem. – Easy to setup.
  48. Unit tests with Dagger • Test piece of code while

    all it’s dependencies are mocked. • Mockito + JUnit +AssertJ to resuce
  49. Class under test public class LaunchActivityViewModel extends AbstractBaseViewModel { private

    final TripRepository tripRepository; public PublishRelay<String> createTrip = PublishRelay.create(); private BehaviorSubject<Trip> tripIdSubject = BehaviorSubject.create(); @Inject public LaunchActivityViewModel(TripRepository tripRepository) { this.tripRepository = tripRepository; initializeCommands(); } @Override public void initializeCommands() { createTrip .map(trip -> tripRepository.createTrip(trip)) .subscribe(tripIdSubject::onNext); } }
  50. Identify dependencies public class LaunchActivityViewModel extends AbstractBaseViewModel { private final

    TripRepository tripRepository; public PublishRelay<String> createTrip = PublishRelay.create(); private BehaviorSubject<Trip> tripIdSubject = BehaviorSubject.create(); @Inject public LaunchActivityViewModel(TripRepository tripRepository) { this.tripRepository = tripRepository; initializeCommands(); } @Override public void initializeCommands() { createTrip .map(trip -> tripRepository.createTrip(trip)) .subscribe(tripIdSubject::onNext); } }
  51. Mock dependencies @RunWith(JUnit4.class) public class LaunchActivityViewModelTest { @Mock TripRepository tripRepository;

    LaunchActivityViewModel launchActivityViewModel; @Before public void setUp() { MockitoAnnotations.initMocks(this); launchActivityViewModel = new LaunchActivityViewModel(tripRepository); } @Test public void testTripSubscriber() { … } }
  52. Functional/Integration Tests with Dagger • Create graph for each flavor

    and add modules with real or mock objects as needed. • Define a common interface without component annotation.
  53. Functional/Integration Tests with Dagger? • Create graph for each flavor

    and add modules with real or mock objects as needed. • Define a common interface without component annotation. • Build flavor specific components extending common interface.
  54. Flavor - Prod @PerApplication @Component(modules = { ApplicationModule.class, LocationVendorModule.class, NetworkModule.class

    }) public interface ApplicationComponent extends GratisPilotComponent { Context context(); Realm realm(); }
  55. Flavor - Mock @PerApplication @Component(modules = {MockApplicationModule.class, MockLocationVendorModule.class}) public interface

    TestApplicationComponent extends GratisPilotComponent { Context context(); StatusValidator statusValidator(); }
  56. Flavor - Mock @PerApplication @Component(modules = {MockApplicationModule.class, MockLocationVendorModule.class}) public interface

    TestApplicationComponent extends GratisPilotComponent { Context context(); StatusValidator statusValidator(); }
  57. Functional/Integration Tests with Dagger? • Create graph for each flavor

    and add modules with real or mock objects as needed. • Define a common interface without component annotation. • Build flavor specific components extending common interface. • Build flavor specific modules.
  58. Prod Flavor - Module @Module public class LocationVendorModule { @Provides

    @PerApplication GoogleApiClient providesGoogleApiClient(Context context) { return new GoogleApiClient .Builder(context) .addApi(Places.GEO_DATA_API) .addApi(Places.PLACE_DETECTION_API) .build(); } }
  59. Mock Flavor - Module @Module public class MockLocationVendorModule { @Provides

    @PerApplication GoogleApiClient providesGoogleApiClient() { return mock(GoogleApiClient.class); } }
  60. Functional/Integration Tests with Dagger? • Create graph for each flavor

    and add modules with real or mock objects as needed. • Define a common interface without component annotation. • Create flavor specific components extending common interface. • Create flavor specific modules. • Extend main application class for your flavor and overwrite method to build graph.
  61. Prod Flavor – Application Class public class GratisPilotApplication extends Application

    { private final GratisPilotComponent component = createComponent(); @Override protected GratisPilotComponent createComponent() { return DaggerApplicationComponent.builder() .applicationModule(new ApplicationModule(this)).build(); } }
  62. Prod Flavor – Application Class public class GratisPilotApplication extends Application

    { private final GratisPilotComponent component = createComponent(); @Override protected GratisPilotComponent createComponent() { return DaggerApplicationComponent.builder() .applicationModule(new ApplicationModule(this)).build(); } }
  63. Mock Flavor - Application Class public class MockApplication extends GratisPilotApplication

    { @Override protected GratisPilotComponent createComponent() { return DaggerTestApplicationComponent.builder(). mockApplicationModule(new MockApplicationModule(this)).build(); } }
  64. Mock Flavor - Application Class public class MockApplication extends GratisPilotApplication

    { @Override protected GratisPilotComponent createComponent() { return DaggerTestApplicationComponent.builder(). mockApplicationModule(new MockApplicationModule(this)).build(); } }
  65. Why NOT? • It force dagger to resolve the parameters

    and it’s dependencies even though your method returns mock. • Binding methods cannot be static.
  66. No scope on class, not defined in module public class

    classA { @Inject DemoClass demoClassInClassA; } public class classB { @Inject DemoClass demoClassInClassB; } public class DemoClass { @Inject public DemoClass() { } } How many instance of DemoClass will be created? 2 Why? - Since the binding is not scoped, dagger will generate simple factory for this class that will return new object for every injection.
  67. Class is scoped, not defined in module @PerApplication public class

    DemoClass { @Inject public DemoClass() { } } public class classA { @Inject DemoClass demoClassInClassA; } public class classB { @Inject DemoClass demoClassInClassB; } How many instance of DemoClass will be created? 1 Why? Dagger creates ScopedProvider.create(DemoClass_Factory.create()); which will maintain scope as long as new component is not created.
  68. Class not scoped, not scoped in module public class DemoClass

    { @Inject public DemoClass() { } } //Module @Provides DemoClass demoClass() { return new DemoClass(); } public class classA { @Inject DemoClass demoClass; } public class classB { @Inject DemoClass demoClass; } How many instance of DemoClass will be created? 2
  69. Class scoped, not scoped in module @PerApplication public class DemoClass

    { @Inject public DemoClass() { } } //Module @Provides DemoClass demoClass() { return new DemoClass(); } How many instance of DemoClass will be created? 2 Tip: Binding provided by @Module takes precedence. public class classA { @Inject DemoClass demoClass; } public class classB { @Inject DemoClass demoClass; }
  70. Class not scoped, scoped in module //@PerApplication public class DemoClass

    { @Inject public DemoClass() { } } //Module @Provides @PerApplication DemoClass demoClass() { return new DemoClass(); } How many instance of DemoClass will be created? 1 public class classA { @Inject DemoClass demoClass; } public class classB { @Inject DemoClass demoClass; } Given - @Component is scoped with @PerApplication
  71. Class scoped & scoped in module @PerApplication public class DemoClass

    { @Inject public DemoClass() { } } //Module @Provides @PerApplication DemoClass demoClass() { return new DemoClass(); } How many instance of DemoClass will be created? 1 public class classA { @Inject DemoClass demoClass; } public class classB { @Inject DemoClass demoClass; } Given - @Component is scoped with @PerApplication
  72. Class scoped & scoped in module @PerApplication public class DemoClass

    { @Inject public DemoClass() { } } //Module @Provides @PerActivity DemoClass demoClass() { return new DemoClass(); } How many instance of DemoClass will be created? public class classA { @Inject DemoClass demoClass; } public class classB { @Inject DemoClass demoClass; } Error – Binding in module can be without scope or same scope as Component. So here it can only be @PerApplication or nothing. Given - @Component is scoped with @PerApplication
  73. Not specified in module public final class LogExporter { private

    Context context; //@Inject public LogExporter(Context context) { this.context = context; } } How many instance of LogExporter will be created? Error - Cannot be provided without an @Inject constructor or from an @Provides- annotated method.
  74. History • Build on JSR 330 – October 09 •

    Google Guice 3.0 – March 11 • Dagger 1 – May 2013 • Dagger 2 – Apr 2015