Dependency Injection and Dagger2 Advance

Dependency Injection with Dagger2 Advance

This is a continuation of my Dependency Injection and Dagger2 blog but in an advanced version. To understand this blog it is mandatory to read the previous blog if you haven't read it. Here, is the link

Dependency Injection and Dagger2

Here, we will read about Sub-Component, Qualifiers, Scopes, and Binds vs Provides, etc. Let's see them one by one.

Binds in Dagger2:

Binds do the same as @Provides, it also creates the object to inject. Binds never create an implementation of a class or method, it just instantiates this method directly. Thus, we use abstract class and abstract method to use @Binds . This provides better performance.

@Provides create first ModuleFactory class, then module and then create inject object whereas binds directly create inject object. It reduces the number of lines of code.

But the problem is- Binds only accept a single argument with the return type.

The example is given below-

@Module
abstract class PureMathModule{
    @Binds
    abstract Math provideMath(PureMath pureMath);
}
You can’t use @provide methods on the same module shown above. But we can use static provides method here.

How to implement Interface in Dagger2:

Let’s suppose Math is an interface then we can’t @inject its method directly. But we can create a class that will implement this interface.

interface Math {
    public void mathBook();
}

Let's suppose we implement this interface in 2 classes:

public class PureMath implements Math
{
    @Inject
    public PureMath(){ }
    
    @Override
    public void mathBook(){ }
}
public class BusinessMath implements Math{
    
    @Inject
    public BusinessMath(){ }
    @Override
    public void mathBook(){ }
}

Now, calling Math interface in Subject class as shown below:

public class Subject {
    private Math math ;
    @Inject
    public Subject(Math math){}

    public void read(){ }
}

Then, the Subject class will get confused about what Math child class should it inject in the Subject constructor. To resolve this problem we would create modules. The module will provide only that particular class which we want to call. As shown below:

@Module
public class PureMathModule{
    @Provides
    Math provideMath(PureMath pureMath){
        return pureMath;
    }
}

And now add a module in component:

@Component(modules = {ScienceModule.class, PureMathModule.class})
 public interface SubjectComponent{
        Subject getSubject();

        Void inject(SubjectActivity activity);
        }

We can’t use PureMathModule and BusinessMathModule in the same component then the same problem will arise. Throws a compile-time error Math binds multiple times. Now, the Subject class will automatically bind with the PureMathModule.

Inject values at runtime with UI in Dagger2:

Suppose, if values are assigned through User Interface i.e. at run time then how it gets handled. Suppose, we have the book name at runtime. Let’s see by taking the previous example:

public class PureMath implements Math{ 
    private String bookName;

    //here, must removed @Inject as the value is passed at the runtime
    public PureMath(String bookName){
        this.bookName = bookName;
    }
    @Override
    public void mathBook(){ }
}

Now, create the provideBookName() method to provide the book name than with the component.

public class PureMathModule{
    private String bookName;
    public PureMathModule(String bookName){
        this.bookName = bookName;
    }
    @Provides
    String provideBookName(){
        return bookName;
    }

    @Provides
    Math provideMath(PureMath pureMath){
        return pureMath;
    }
}

Now, to call DaggerComponent we write-

DaggerSubjectComponent.builder()
         .pureMathModule("Book Name")
         .build()
         .inject(this);
The difference between DaggerComponent create() and in build() is - create() works when no runtime argument is passed into the constructor, else we use build() method.

Inject values By using @Component.Builder and @BindsInstance

We can also pass our value to a builder directly by using a nested interface in our component.

But it has some rules to follow:

  • Method that is bind with BindInstance must return the builder type. It should not have more than one parameter. If you want to bind more dependencies must create different methods for each dependency.
  • The builder must have at least one method which returns Component or super Component.

An advantage of using Component. Builder is, if we forget to supply some dependency to the builder, it will blow up at runtime time.

Example as given below-

@Component(module = {ScienceModule.class, PureMathModule.class})
public interface SubjectComponent{
        Subject getSubject();

        Void inject(SubjectActivity activity);

     // Here, by using normal java builder pattern, we are overriding builder definition here,
     @Component.Builder
     interface Builder{
          
     @BindsInstance
     Builder bookName(String bookName);
     
        SubjectComponent build();
        }
        }

Change in calling DaggerComponent:

DaggerSubjectComponent.builder()
        .bookName(“Book Name”)
        .build()
        .inject(this);

Inject values by using @Component.Factory

It works on the factory design pattern.

But it also has some rules to follow:

  • Factory cannot have more than one method but accepts more than one parameter.
  • If you want to bind more than one dependencies than you don't have to create methods for each dependency. You just pass a new parameter for each of them. This reduces the chaining of methods.
  • Your method must return the type of Component or supertype of Component.

The main advantage of using @Component.Factory is, if we forget to supply some dependency to the factory, it will blow up at compile time. Remove chaining of methods.

Example as given below-

@Component(module = {ScienceModule.class, PureMathModule.class})
public interface SubjectComponent{
     Subject getSubject();

     Void inject(SubjectActivity activity);
     
     
     @Component.Factory
     interface Factory{
          Factory  bookName(@BindsInstance String bookName);
          SubjectComponent create();

     }
}

Change in calling DaggerComponent:

DaggerSubjectComponent.factory()
        .bookName(“Book Name”)
        .create()
        .inject(this);

@Name in Dagger:

If we have to pass more than one parameter with the same data type then dagger doesn’t understand, so, we differentiate it with @Named(“ ”) convention in Component as well as in Inject constructor also, for example.

@Component(module = {ScienceModule.class, PureMathModule.class})
public interface SubjectComponent{
        Subject getSubject();

        Void inject(SubjectActivity activity);

          @Component.Builder
          interface Builder{
          @BindsInstance
          Builder  bookName(@Named(“Name_One”) String bookName);
          @BindsInstance
          Builder  bookNameTwo(@Named(“Name_Two”) String bookNameTwo);

          SubjectComponent build();
        }
        }

Scopes

The scopes are java annotations. It allows you to “preserve” the object instance and provide it as a “local singleton” for the duration of the scoped component.

Singleton Scope:

Singleton is a scope. It facilitates to create a single instance of the same class. It stays in memory until the application alive. Example- In the component, we write-

@Singleton
@Component(module = {ScienceModule.class, PureMathModule.class})
public interface SubjectComponent{
     Subject getSubject();

     Void inject(SubjectActivity activity);
}

If your class is not injected directly, it called from @provides method then @Singleton should be written above provide method also. As shown below-

@Module
class ScienceModule{

    @Singleton
    @Provides
    ScienceBook provideScienceBooks(){
        //do something
        return new  ScienceBook();
    }
}

Custom Scopes in dagger2:

It is also called as local scopes. As Singleton scope object stays in memory until the application is alive. Thus, it may create a memory leak issue. So, we must create a custom scope to overcome this problem. Custom scope only exists with certain components of the app. It tells the object you are going to exist until that particular activity or component exists. Below code shows how to create a custom scope of Science class whose instance want to share across components:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ScienceScope {
}

Then use this scope in the above component class and providers:

@ScienceScope
@Component(module = {ScienceModule.class, PureMathModule.class})

Qualifiers

Qualifiers are annotations. As dagger understands only return type. So @Qualifier is used to differentiate the instance of different methods with the same return type. The example is given below-

Create a custom qualifier interface:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface ScienceQualifier {
}

Now, use it in the module as shown below:

@Module
class ScienceModule{

    @ScienceQualifier
    @Singleton
    @Provides
    ScienceBook provideScienceBooks(){
        //do something
        return new  ScienceBook();
    }
}

Subcomponent in dagger2

It inherits and extends the object of a parent component. It divides the parent component objects into sub-objects and encapsulates them. Thus, it can be used by more than one scope. Below are the steps to create and use subcomponent:

  • Let's suppose we have a module for subcomponent:
@Module
public class BookNameModule {
    @Provides
    public void getAllBooksName(){
        //code to get all book’s name
    }
}
  • To create a subcomponent, you need to define Subcomponent. Builder interface providing builder method for each module and build a method that returns component.
@BookScope
@Subcomponent(modules = BookNameModule.class)
public interface BookSubComponent {

    //defining parent dependent inject object, its optional. 
    public void inject(BookActivity activity);
    
    @Subcomponent.Builder
    interface Builder {
        Builder BookNameModule(BookNameModule module);
        BookSubComponent build();
    }
}
  • Now defining parent component:
@Singleton
@Component(modules={BookModule.class})
public interface BookComponent {
     // defining sub component builder and inject method   
     BookSubComponent.Builder bookBuilder();
     public void inject(MainActivity mainActivity);
}
  • To injecting from parent component in MainActivity, we write this line in MainActivity class
DaggerBookComponent.create().inject(this);
  • To inject object from sub-component:
public class BookActivity extends AppCompatActivity {
    @Inject
    BookNameModule getAllBooksName;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    DaggerBookComponent.create().bookBuilder().build.inject(this);
    }
}

Component builder method exposed in parent component also, so, we can inject objects from parent component too.

I hope this blog will help you to give a clear idea of Dagger2.

Do share this blog with your fellow developers to spread the knowledge.

Happy Learning!