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.

Avatar for Mayank Mehta

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