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

A (K)night with Dagger

Chintan Soni
November 05, 2017

A (K)night with Dagger

Presented this talk at GDG DevFest Ahmedabad 2017.
Dagger2 is been here since 2015, though average developers are not able to get the underlying architecture and its usage. This talk covers deep understanding about annotation processors, how Dagger2 works under the hood, how to rebuild current application architecture with Dagger2 and detailing about Dagger2 annotations.

Find original presentation with animations at: https://goo.gl/N3YYZQ

Chintan Soni

November 05, 2017
Tweet

More Decks by Chintan Soni

Other Decks in Technology

Transcript

  1. Dependency • Class Black needs object from class Red, Green

    and Blue, to fulfil its purpose. • It implies that class Black is dependent on Red, Green and Blue. • So, Dependency is an object that can be used.
  2. Dependency Injection • Injection: Passing of one or more objects

    to the depending class • Dependency Injection: A form of Inversion of Control, a design principle, which say that any class should be getting its dependencies from outside. • In other words, class should not be instantiating other class within it, but rather there should be another configuration class which can provide with those instances.
  3. So, what does that mean ? You can... • Write

    testable code • Decouple class and its dependency • Design maintainable and reusable structure
  4. Reflection... • Enables the code with ability to examine or

    modify the Runtime behavior of class in runtime. • Java Reflection Api was introduced in Java 1.5 • Drawbacks: ◦ Performance Overhead ◦ Exposure of Internals ◦ Unexpected and Untraceable Crashes due to dependency resolution at runtime
  5. Example of Reflection • Packages that provide classes for reflection:

    ◦ java.lang ◦ java.lang.reflect • Class class: ◦ MainActivity.class.getSimpleName() returns “MainActivity” using Reflection
  6. History of Dagger1 • Square Inc.’s Dagger1 is a DI

    Framework which was purely dependent on Java Reflection Api • Typically, you are not gonna use DI for injecting just one dependency • So, more injections implies more performance overhead • Also, error tracing was extremely difficult
  7. But, thanks to Google Google undertook this project and made

    revolutionary enhancements in the underlying mechanism of Dagger1
  8. Annotation is... • A class of metadata, introduced in Java

    1.5, that can be associated with class, methods, fields, and even other annotations. • Those meta data can be accessed at runtime via Reflection. • But we were not known to the same capability of reading metadata at compile time via Annotation Processors.
  9. Annotation Processors... • Are code generators that eliminate boilerplate code

    by generating code for you at Compile Time. Really..!! • No performance Overheads at all (Compile Time nature). • Even you can create your own Annotations and a Processor for it using this tutorial: The 10 step guide to annotation processing in android studio
  10. How Dagger 2 differs from Dagger 1 • Works purely

    on Annotation Processor • “Which client is dependent on what objects, that is resolved at compilation stage.” • Hence, Dagger2 is free from Performance Overhead. Also, Dagger2 errors are highly traceable.
  11. Modes of DI • Java Specification Request 330 (JSR 330)

    has defined standard Java Annotations for describing dependencies of a class. • Modes of DI: ◦ Constructor Injection: Injecting the constructor parameters ◦ Field Injection: Injecting the member variable (Required that it must not be private) ◦ Method Injection: Injecting the method parameter
  12. By the way… Some Annotation Processors.. Jake Wharton’s ButterKnife is

    an Annotation Processor. @Bind(R.id.textView) TextView mTextView; Android Architecture Component’s Room Persistence Library is an Annotation Processor @Entity public class User {...} @Dao public interface UserDao {...} @Database(entities = {User.class}, version = 1) public abstract class AppDatabase extends RoomDatabase {...}
  13. But I am really not happy with it. • Firstly,

    code is ought to be in serialized in some way. Every objects need to be initialized in some order. • Secondly, Code looks very clumsy • And lastly, Code is not test friendly
  14. Two major problems to be solved.. • Ideal way of

    dealing with clumsy initializations ? • Make our code more testable ?
  15. Initial Setup Towards Dagger 2 implementation • Identify the classes

    who secretly depend on objects. • Provide dependency of such objects through constructor arguments • Do necessary object creations at one level, let’s say, at Application Level. • Example: We are gonna provide single point of accessing Picasso instance. So, class dependent on Picasso, should be having Picasso as constructor arguments.
  16. Make sure to add below Dependencies dependencies { implementation 'com.google.dagger:dagger:2.11'

    annotationProcessor 'com.google.dagger:dagger-compiler:2.11' }
  17. Annotations with Dagger2 • @Module and @Provides: define classes and

    methods which provide dependencies • @Inject: request dependencies. Can be used on a constructor, a field, or a method • @Component: enable selected modules and used for performing dependency injection
  18. Component is... • A public interface to your dependency graph

    • Best Practise: Exposes only your top level dependency. Internal dependencies should remain under the hood. • Go ahead creating a component RandomUserComponent
  19. @Component: Tells Dagger that this interface is a Dagger Component

    and not an ordinary interface. Dagger will generate a subclass of this component with implementation of getPicasso() and getRandomUserService()
  20. But how will Component know from where to get the

    dependencies of Picasso and RandomUserService ?
  21. Modules... • provide those under the hood dependencies to the

    outermost dependency, like Picasso and RandomUserService. • Go ahead with moving my code from RandomUserApplication class to these modules as OkHttpClientModule, PicassoModule, RandomUserServiceModule. • @Provides: If any method in module provides dependency, it should be annotated with @Provides
  22. We are all set with our modules, But, PicassoModule and

    OkHttpClientModule are incomplete without Context…
  23. Manage External Dependencies • External Dependencies: Dependencies on which our

    graph itself is dependent on. • Our modules, now, are just lacking with the dependency of Context. • Go ahead with Creating a ContextModule
  24. • External Dependencies to our graph or module can be

    of Context, Activity, Service etc. • They are external because WE NEVER CREATE CONTEXT / ACTIVITY / SERVICE INSTANCE BY NEW KEYWORD… • Remember this…
  25. Modules with includes attribute Includes attribute: includes other module dependency

    to the current module So, my module’s @Module annotation got updated with includes attribute as: @Module(includes = OkHttpClientModule.class) public class RandomUserServiceModule {} @Module(includes = OkHttpClientModule.class) public class PicassoModule {} @Module(includes = ContextModule.class) public class OkHttpClientModule {}
  26. By now, our modules are connected and can communicate to

    each other. We are simply remaining to tell our Component that which Modules you need to depend on..
  27. Component annotation has modules attribute • Modules attribute: provides a

    way to tell component about its dependent modules. • So, my component’s @Component annotation got updated with modules attribute as: @Component(modules = {RandomUserServiceModule.class, PicassoModule.class}) public interface RandomUserApplicationComponent { ... }
  28. And if you are lucky... • Dagger generates a class

    with Builder Design Pattern DaggerRandomUserApplicationComponent which now provides a way to initialize any component with modules associated with it. • Magic of Dagger: For a particular component, you only need to pass modules that depend on external dependencies, you can skip providing a component with internal dependent modules.
  29. Every time you call Component#build() • Dagger by default, creates

    new instances of every dependency to be injected. • Why Dagger doesn’t understand that I just need a single instance to be created for Picasso ? • How do we tell Dagger to create just a single instance for any dependency ?
  30. Scope... • Annotation informs Dagger to share the same instance

    every time Component.build() will be called. • It will make dependencies work as a Singleton for that particular Component.
  31. This is how we use @Scope @Scope @Retention(RetentionPolicy.CLA SS) public

    @interface RandomUserApplicationScope { } This is how we will use our customly created Scope: • Firstly, we have to put it at component level: @RandomUserApplicationScope @Component(modules = {RandomUserServiceModule.class, PicassoModule.class}) public interface RandomUserApplicationComponent {} • Then, put it on every method with @Provides, that needs to act as Singleton. Usually, we need just single instance for OkhttpClient, Picasso, etc: @Provides @RandomUserApplicationScope public Picasso picasso(Context context, OkHttp3Downloader okHttp3Downloader) {} @Provides @RandomUserApplicationScope public Retrofit retrofit(OkHttpClient okHttpClient, GsonConverterFactory gsonConverterFactory) {}
  32. Typically, we need two type of Contexts: Application and Activity.

    We can have ActivityModule to provide Activity context.
  33. • But Dagger gets confused which Context to use because

    it has got two modules that has methods that provide Context and it will throw error on build. • How can we tell Dagger that this dependency should use Application Context or Activity Context ?
  34. @Named annotation does this job We can specify @Named(“”) annotation

    as: In ContextModule: @Provides @RandomUserApplicationScope @Named("application_context") public Context context() { return context.getApplicationContext(); } In ActivityModule: @Provides @RandomUserApplicationScope @Named("activity_context") public Context context() { return context; }
  35. Tell Dagger to use Application context as In OkHttpModule: @Provides

    @RandomUserApplicationScope public File file(@Named("application_context") Context context) {} In PicassoModule: @Provides @RandomUserApplicationScope public Picasso picasso(@Named("application_conte xt") Context context, OkHttp3Downloader okHttp3Downloader) {}
  36. @Qualifier is of my type Create an interface as: @Qualifier

    public @interface ApplicationContext { } @Provides @RandomUserApplicationScope @ApplicationContext public Context context() { return context; } Use it in OkHttpModule and PicassoModule: @Provides @RandomUserApplicationScope public File file(@ApplicationContext Context context) {} @Provides @RandomUserApplicationScope public Picasso picasso(@ApplicationContext Context context, OkHttp3Downloader okHttp3Downloader) {}
  37. Till now... • We have created Application Level Dependencies •

    But what about some dependencies that we need just on Activity Level ? • Activity Life Cycle runs separate from Application Life Cycle • So, dependencies created within Activity should be destroyed with Activity itself. • Let’s understand Multiple Components and communication between them
  38. Creating Multiple Components and make them talk • Best practises:

    When you are injecting dependencies into clients who have life cycle different from where dependencies are coming, it's better to create a separate component and modules for that client.
  39. Firstly, Let’s have activity-level scope @Scope public @interface MainActivityScope {

    } • We will apply this scope to component and modules we will create next • So, let’s have a MainActivityComponent and make it talk to RandomUserApplicationComponent for dependencies they are missing.
  40. • Dependencies attribute: It tells Dagger that while creating component,

    if you are looking for any other dependencies, you can look into components specified in the dependencies attribute. • So, now MainActivityComponent can look for dependencies into RandomUserApplicationComponent
  41. What if I have 50 dependencies in your component ?

    Would write 50 different lines ? randomUserService = mainActivityComponent.randomUserService(); randomUserAdapter = mainActivityComponent.randomUserAdapter(); ... Well, I am lazy at this and we are already learning lazy loading of objects.
  42. You might think.. “Ohh.. I don’t care. I am good

    at writing utility classes... ”
  43. @Inject is waiting for you... • @Inject : JSR 330

    (Java Specification Requests) provides this annotation • Instead of telling Dagger, that I want RandomUserAdapter and RandomUserService instances, let Dagger react upon fields with @Inject annotations. • But to use the magic of @Inject, we will need to modify our code a little.
  44. How will this work ??? • Now, when Dagger finds

    a method in MainActivityComponent with void as return type, “Omg… there’s no return type…!!! There must be something I need to look into this class. Ohh yes, I can see fields annotated with @Inject, let me initialize them” • And you are done.. You can run your code
  45. Chintan Soni Senior Android Developer @ Simform Solutions Pvt. Ltd.

    Follow me on: FB: chintansoni202 Twitter: @chintansoni202